// piece.dart

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:puzzleweave/play/board.dart';
import 'package:logging/logging.dart';
import 'package:vector_math/vector_math.dart' as vmath;

final Logger _log = Logger('piece.dart');

const double _outLineOffset = 0.5;
const double _innerLineOffset = 1.5;

// piece分组数据结构
class PieceGroup {
  List<Piece> pieces = [];

  int get length => pieces.length;

  // 群组的组合路径缓存 (路径是相对坐标，相对于群组基准点)
  Path? groupClipPath;
  Path? groupOutLinePath;
  Path? groupInnerLinePath;

  void add(Piece piece) {
    if (!pieces.contains(piece)) {
      pieces.add(piece);
    }
    // 确保 piece 的 group 指向自己
    piece.group = this;
    clearPathCache(); // 群组变化，清空缓存
  }

  void remove(Piece piece) {
    if (pieces.contains(piece)) {
      pieces.remove(piece);
    }
    piece.group = null;
    clearPathCache(); // 群组变化，清空缓存
  }

  bool contains(Piece piece) {
    return pieces.contains(piece);
  }

  // 判断本group是否包含other group
  bool containsGroup(PieceGroup otherGroup) {
    for (var p in otherGroup.pieces) {
      if (!pieces.contains(p)) {
        return false;
      }
    }
    return true;
  }

  // 网格坐标边界计算辅助属性
  int get _minR => pieces.map((p) => p.curRow).reduce(min);
  int get _maxR => pieces.map((p) => p.curRow).reduce(max);
  int get _minC => pieces.map((p) => p.curCol).reduce(min);
  int get _maxC => pieces.map((p) => p.curCol).reduce(max);
  // -----------------------------

  // 群组中心点（绝对坐标）
  Offset get center {
    if (pieces.isEmpty) return Offset.zero;

    final board = pieces[0].board;
    double minX = double.infinity, minY = double.infinity;
    double maxX = -double.infinity, maxY = -double.infinity;

    for (var piece in pieces) {
      // 碎片当前在 Canvas 上的左上角坐标
      final x = piece.transform.storage[12];
      final y = piece.transform.storage[13];
      final w = board.pieceLogicalWidth;
      final h = board.pieceLogicalHeight;

      minX = min(minX, x);
      minY = min(minY, y);
      maxX = max(maxX, x + w);
      maxY = max(maxY, y + h);
    }

    // 计算边界框中心点
    return Offset((minX + maxX) / 2, (minY + maxY) / 2);
  }

  // 计算群组的左上角碎片（以 curRow/curCol 为准）
  Piece get topLeftPiece {
    if (pieces.isEmpty) throw Exception("Group is empty");

    // 找到最上面，然后最左边的格子作为起点
    return pieces.reduce((a, b) {
      if (a.curRow < b.curRow) return a;
      if (b.curRow < a.curRow) return b;
      if (a.curCol < b.curCol) return a;
      return b;
    });
  }

  // --- 新增辅助方法：用于不规则群组路径追踪 ---

  // 检查指定网格坐标 (r, c) 是否有碎片属于本群组
  // r, c 是绝对网格坐标 (curRow, curCol)
  bool _pieceAtCurCoord(int r, int c) {
    if (r < 0 || c < 0) return false;

    // 只需要检查群组内的碎片
    for (var piece in pieces) {
      if (piece.curRow == r && piece.curCol == c) {
        return true;
      }
    }
    return false;
  }

  // 检查 (r, c) 单元格的指定边是否是群组的外部边界
  // r, c 是绝对网格坐标 (curRow, curCol)
  bool _isExternalGroupBoundary(int r, int c, String side) {
    if (!_pieceAtCurCoord(r, c)) {
      return false; // 该坐标没有碎片属于这个群组，不可能是边界
    }

    final board = pieces[0].board;
    switch (side) {
      case 'top':
        if (r == 0) return true; // Board top
        return !_pieceAtCurCoord(r - 1, c); // 上邻居不在群组内
      case 'right':
        if (c == board.cols - 1) return true; // Board right
        return !_pieceAtCurCoord(r, c + 1); // 右邻居不在群组内
      case 'bottom':
        if (r == board.rows - 1) return true; // Board bottom
        return !_pieceAtCurCoord(r + 1, c); // 下邻居不在群组内
      case 'left':
        if (c == 0) return true; // Board left
        return !_pieceAtCurCoord(r, c - 1); // 左邻居不在群组内
      default:
        return false;
    }
  }

  // 生成群组整体路径（裁剪/边框的核心逻辑）
  Path _generateIrregularGroupPath({required bool isClipPath, double offset = 0}) {
    if (pieces.isEmpty) return Path();

    final path = Path();
    final board = pieces[0].board;
    final w = board.pieceLogicalWidth;
    final h = board.pieceLogicalHeight;

    final r = board.cornerRadius;

    // 1. 确定起始 Piece 和起始点
    Piece startPiece = topLeftPiece;
    Piece loopPiece = startPiece;

    // 群组本地坐标系下的起始点 (top-left corner of the top-left piece)
    final startRelX = (startPiece.curCol - _minC) * w;
    final startRelY = (startPiece.curRow - _minR) * h;

    // 决定起点 MoveTo 坐标 (Group Local)
    double initialX = startRelX + offset;
    double initialY = startRelY + offset;

    // 初始方向：向右 (Tracing Top edge)
    String direction = 'right';

    final hasStartTop = _isExternalGroupBoundary(startPiece.curRow, startPiece.curCol, 'top');
    final hasStartLeft = _isExternalGroupBoundary(startPiece.curRow, startPiece.curCol, 'left');

    if (hasStartTop && hasStartLeft) {
      path.moveTo(initialX + r, initialY);
      initialX += r;
    } else if (hasStartTop) {
      path.moveTo(initialX, initialY);
    } else if (hasStartLeft) {
      // 这种情况意味着左边是外部边界，但上面是内部边界，路径起点在左边中央
      path.moveTo(initialX, initialY + r);
      initialY += r;
    } else {
      // TL 角是内部的，但 topLeftPiece 保证是群组最左上角，因此至少有一条边是外部边界
      // 如果两边都是内部，说明群组内部有空隙，但此处假设群组是连通的。
      path.moveTo(initialX, initialY);
    }

    // 当前路径点 (Group Local)
    double currentX = initialX;
    double currentY = initialY;

    // Safety break
    int maxIterations = pieces.length * 8; // 增加安全上限
    int iteration = 0;

    // --- 路径追踪循环 (Wall Follower - Keep Group on Left) ---
    do {
      iteration++;
      if (iteration > maxIterations) {
        _log.severe('Group path tracing failed to close after $maxIterations iterations. Breaking.');
        break;
      }

      final absR = loopPiece.curRow;
      final absC = loopPiece.curCol;
      final relC = absC - _minC;
      final relR = absR - _minR;

      // 当前 Piece 的边界坐标 (Group Local) - 包含偏移
      final x0 = relC * w + offset; // Left
      final y0 = relR * h + offset; // Top
      final x1 = (relC + 1) * w - offset; // Right
      final y1 = (relR + 1) * h - offset; // Bottom

      String nextDirection = '';
      Piece? nextPiece;
      bool isCornerRounded = false;

      // 检查当前方向的边是否是外部边界
      bool isExternalEdge = false;
      switch (direction) {
        case 'right':
          isExternalEdge = _isExternalGroupBoundary(absR, absC, 'top');
          break;
        case 'down':
          isExternalEdge = _isExternalGroupBoundary(absR, absC, 'right');
          break;
        case 'left':
          isExternalEdge = _isExternalGroupBoundary(absR, absC, 'bottom');
          break;
        case 'up':
          isExternalEdge = _isExternalGroupBoundary(absR, absC, 'left');
          break;
      }

      if (isExternalEdge) {
        // --- 1. Edge Segment Drawing ---
        if (direction == 'right') {
          // Tracing Top edge to TR corner (x1, y0)
          final hasR = _isExternalGroupBoundary(absR, absC, 'right');
          isCornerRounded = hasR;
          final targetX = x1;
          final targetY = y0;

          // LineTo segment
          final lineToX = isCornerRounded ? targetX - r : targetX;
          if (currentX < lineToX) {
            // 避免重复 LineTo
            path.lineTo(lineToX, targetY);
            currentX = lineToX;
            currentY = targetY;
          }

          // Arc for TR corner
          if (isCornerRounded) {
            path.arcToPoint(Offset(targetX, targetY + r), radius: Radius.circular(r), clockwise: true);
            currentX = targetX;
            currentY = targetY + r;
          } else {
            currentX = targetX;
            currentY = targetY;
          }

          // --- 2. Decision: Turn Down or Move Right ---
          nextPiece = board.getPieceByCoordinate(absR, absC + 1);
          bool rightNeighborInGroup = nextPiece != null && pieces.contains(nextPiece);

          if (hasR) {
            // Right edge exposed -> TURN DOWN (Stay on loopPiece)
            nextDirection = 'down';
          } else if (rightNeighborInGroup) {
            // Right edge internal -> MOVE RIGHT (Change loopPiece)
            loopPiece = nextPiece!;
            nextDirection = 'right';
          } else {
            // Board boundary or outer space (R is external) -> TURN DOWN (This case is covered by hasR=true, but as a fallback)
            nextDirection = 'down';
          }
        } else if (direction == 'down') {
          // Tracing Right edge to BR corner (x1, y1)
          final hasB = _isExternalGroupBoundary(absR, absC, 'bottom');
          isCornerRounded = hasB;
          final targetX = x1;
          final targetY = y1;

          // LineTo segment
          final lineToY = isCornerRounded ? targetY - r : targetY;
          if (currentY < lineToY) {
            path.lineTo(targetX, lineToY);
            currentX = targetX;
            currentY = lineToY;
          }

          // Arc for BR corner
          if (isCornerRounded) {
            path.arcToPoint(Offset(targetX - r, targetY), radius: Radius.circular(r), clockwise: true);
            currentX = targetX - r;
            currentY = targetY;
          } else {
            currentX = targetX;
            currentY = targetY;
          }

          // --- 2. Decision: Turn Left or Move Down ---
          nextPiece = board.getPieceByCoordinate(absR + 1, absC);
          bool bottomNeighborInGroup = nextPiece != null && pieces.contains(nextPiece);

          if (hasB) {
            // Bottom edge exposed -> TURN LEFT
            nextDirection = 'left';
          } else if (bottomNeighborInGroup) {
            // Bottom edge internal -> MOVE DOWN
            loopPiece = nextPiece!;
            nextDirection = 'down';
          } else {
            nextDirection = 'left';
          }
        } else if (direction == 'left') {
          // Tracing Bottom edge to BL corner (x0, y1)
          final hasL = _isExternalGroupBoundary(absR, absC, 'left');
          isCornerRounded = hasL;
          final targetX = x0;
          final targetY = y1;

          // LineTo segment
          final lineToX = isCornerRounded ? targetX + r : targetX;
          if (currentX > lineToX) {
            path.lineTo(lineToX, targetY);
            currentX = lineToX;
            currentY = targetY;
          }

          // Arc for BL corner
          if (isCornerRounded) {
            path.arcToPoint(Offset(targetX, targetY - r), radius: Radius.circular(r), clockwise: true);
            currentX = targetX;
            currentY = targetY - r;
          } else {
            currentX = targetX;
            currentY = targetY;
          }

          // --- 2. Decision: Turn Up or Move Left ---
          nextPiece = board.getPieceByCoordinate(absR, absC - 1);
          bool leftNeighborInGroup = nextPiece != null && pieces.contains(nextPiece);

          if (hasL) {
            // Left edge exposed -> TURN UP
            nextDirection = 'up';
          } else if (leftNeighborInGroup) {
            // Left edge internal -> MOVE LEFT
            loopPiece = nextPiece!;
            nextDirection = 'left';
          } else {
            nextDirection = 'up';
          }
        } else if (direction == 'up') {
          // Tracing Left edge to TL corner (x0, y0)
          final hasT = _isExternalGroupBoundary(absR, absC, 'top');
          isCornerRounded = hasT;
          final targetX = x0;
          final targetY = y0;

          // LineTo segment
          final lineToY = isCornerRounded ? targetY + r : targetY;
          if (currentY > lineToY) {
            path.lineTo(targetX, lineToY);
            currentX = targetX;
            currentY = lineToY;
          }

          // Arc for TL corner
          if (isCornerRounded) {
            path.arcToPoint(Offset(targetX + r, targetY), radius: Radius.circular(r), clockwise: true);
            currentX = targetX + r;
            currentY = targetY;
          } else {
            currentX = targetX;
            currentY = targetY;
          }

          // --- 2. Decision: Turn Right or Move Up ---
          nextPiece = board.getPieceByCoordinate(absR - 1, absC);
          bool topNeighborInGroup = nextPiece != null && pieces.contains(nextPiece);

          if (hasT) {
            // Top edge exposed -> TURN RIGHT
            nextDirection = 'right';
          } else if (topNeighborInGroup) {
            // Top edge internal -> MOVE UP
            loopPiece = nextPiece!;
            nextDirection = 'up';
          } else {
            nextDirection = 'right';
          }
        }
      } else {
        // --- Edge is Internal: Move Straight or Turn Right ---
        // This piece's current edge is internal, meaning the external boundary is on the neighbor's side.
        // We must check if we can move *through* the current piece to the next one in the same direction,
        // or if we must turn right immediately because the straight path is blocked by an external boundary.

        // Next piece to check (straight ahead)
        Piece? straightPiece;
        switch (direction) {
          case 'right':
            straightPiece = board.getPieceByCoordinate(absR, absC + 1);
            break;
          case 'down':
            straightPiece = board.getPieceByCoordinate(absR + 1, absC);
            break;
          case 'left':
            straightPiece = board.getPieceByCoordinate(absR, absC - 1);
            break;
          case 'up':
            straightPiece = board.getPieceByCoordinate(absR - 1, absC);
            break;
        }

        bool straightInGroup = straightPiece != null && pieces.contains(straightPiece);

        if (straightInGroup) {
          // Straight piece is in group -> MOVE STRAIGHT (Skip current internal piece)
          loopPiece = straightPiece!;
          nextDirection = direction;
        } else {
          // Straight piece is outside/null, and current edge is internal.
          // This is an internal concave corner. We must turn right to follow the external boundary.
          // Turn right (change direction, stay on current piece)
          switch (direction) {
            case 'right':
              nextDirection = 'down';
              break;
            case 'down':
              nextDirection = 'left';
              break;
            case 'left':
              nextDirection = 'up';
              break;
            case 'up':
              nextDirection = 'right';
              break;
          }
        }
      }

      // Update direction and loopPiece for next iteration
      direction = nextDirection;

      // --- Termination Check ---
      if (currentX == initialX && currentY == initialY) {
        _log.info('Closed path at initial point. Iterations: $iteration');
        break;
      }
    } while (true);

    if (isClipPath) {
      path.close();
    }

    return path;
  }

  // 生成群组裁剪路径（现在支持不规则形状）
  Path _generateGroupClipPath() {
    // 裁剪路径是封闭的，且没有偏移
    return _generateIrregularGroupPath(isClipPath: true);
  }

  // 生成群组边框路径（现在支持不规则形状）
  Path _generateGroupBorderPath(double offset) {
    // 边框路径是开放的（不 close），且有偏移
    return _generateIrregularGroupPath(isClipPath: false, offset: offset);
  }

  // 生成群组整体路径（裁剪+边框）
  void generateGroupPaths() {
    // 使用新的不规则路径生成逻辑
    groupClipPath = _generateGroupClipPath();
    groupOutLinePath = _generateGroupBorderPath(_outLineOffset);
    groupInnerLinePath = _generateGroupBorderPath(_innerLineOffset);
  }

  // 群组整体位移（优化：避免遍历碎片）
  void applyDelta(Offset delta) {
    for (var piece in pieces) {
      piece.transform.storage[12] += delta.dx;
      piece.transform.storage[13] += delta.dy;
    }
  }

  void clearPathCache() {
    groupClipPath = null;
    groupOutLinePath = null;
    groupInnerLinePath = null;
  }

  void print() {
    String str = '======= group size: $length =======\n';
    for (var p in pieces) {
      str += p.toString();
      str += '\n';
    }

    _log.info(str);
  }
}

// 碎片基础数据结构
class Piece {
  final int index;
  final Board board;
  PieceGroup? group;

  // 总计的行数和列数
  final int rows;
  final int cols;

  // 碎片在完整图片中的行/列位置 (逻辑坐标)
  final int row;
  final int col;

  // 碎片当前所处的位置
  int curCol;
  int curRow;

  // 碎片的当前几何变换状态 (包括位置、旋转等)
  vmath.Matrix4 transform;

  // 碎片在原图片中的裁剪矩形 (Source Rect of the image)
  final Rect sourceRect;

  bool get isOK => row == curRow && col == curCol;

  // clipPath, 碎片的裁剪路径（闭合）， 这是为了绘制出圆角效果
  Path? path;
  // 内边框path（可能非闭合）
  Path? innerLinePath;
  // 外边框path (可能非闭合)
  Path? outLinePath;

  // 翻转相关属性
  double flipProgress = 0.0;

  Piece({
    required this.board,
    required this.index,
    required this.row,
    required this.col,
    required this.rows,
    required this.cols,
    required this.sourceRect,
    required this.curCol,
    required this.curRow,
    required this.transform,
  });

  @override
  String toString() => 'Piece($index,$row:$col)';

  Map<String, dynamic> toJson() => {'index': index, 'rows': rows, 'cols': cols, 'row': row, 'col': col, 'curRow': curRow, 'curCol': curCol};

  double get width => board.pieceLogicalWidth;
  double get height => board.pieceLogicalHeight;

  // 辅助函数：获取碎片当前的左上角位置 (tx, ty)
  // 平移分量在 Matrix4.storage 的索引 12 (tx) 和 13 (ty)
  Offset get currentOffset => Offset(transform.storage[12], transform.storage[13]);

  // 辅助函数：获取碎片当前的中心点 (在 Canvas 坐标系中)
  Offset get currentCenter {
    // 碎片的本地中心点
    final Offset localCenter = Offset(width / 2, height / 2);

    // 应用当前变换矩阵
    final vmath.Vector4 transformed = transform.transform(vmath.Vector4(localCenter.dx, localCenter.dy, 0.0, 1.0));

    return Offset(transformed.x, transformed.y);
  }

  // 辅助函数：将碎片移动指定的位移量
  void applyDelta(Offset delta) {
    // 累加 x 轴位移
    transform.storage[12] += delta.dx;
    // 累加 y 轴位移
    transform.storage[13] += delta.dy;
  }

  // 归位， 回到原来的位置
  void revert() {
    transform = board.getTransformByCoordinate(curRow, curCol);
  }

  // 判断当前piece是否可以安置到other的槽位去
  bool canPlaceTo(Piece other) {
    // 1. 如果当前碎片是单独移动，则可以安置
    if (group == null) {
      return true;
    }

    // 当前 piece 与 other piece 的位移
    int dr = other.curRow - curRow;
    int dc = other.curCol - curCol;

    // 判断当前piece的组成员，产生相同的位移后会不会溢出
    for (var p in group!.pieces) {
      int newRow = p.curRow + dr;
      int newCol = p.curCol + dc;
      if (newRow < 0 || newRow >= rows || newCol < 0 || newCol >= cols) return false;
    }
    return true;
  }

  // 判断当前piece是否可以和other piece 合并
  bool canMerge(Piece other) {
    // 原来是邻居， 现在也是邻居，才具备可以合并的基础条件
    if (isNeighbour(other) && isCurNeighbour(other)) {
      // 判断相对位置是否保持一致 (防止错位拼接)
      if (col == other.col) {
        // 同一列 (上/下相邻)
        if ((row - other.row) == (curRow - other.curRow)) return true;
      } else if (row == other.row) {
        // 同一行 (左/右相邻)
        if ((col - other.col) == (curCol - other.curCol)) return true;
      }
    }
    return false;
  }

  /// 创建一个新组, 合并两个piece组
  void groupWith(Piece other) {
    PieceGroup finalGroup = PieceGroup();
    List<Piece> list = [];
    if (group != null) list.addAll(group!.pieces);
    if (other.group != null) list.addAll(other.group!.pieces);
    list.add(this);
    list.add(other);

    // 确保没有重复的 Piece
    final Set<Piece> uniquePieces = list.toSet();

    for (var p in uniquePieces) {
      finalGroup.add(p);
    }
    // 合并后需要重新计算 group paths
    finalGroup.generateGroupPaths();
  }

  // 获取碎片本来的邻居（原正确位置上的邻居）
  List<int> getNeighbourIndexes() {
    List<int> list = [];
    if (row > 0) list.add(index - cols); // 上
    if (row < (rows - 1)) list.add(index + cols); // 下
    if (col > 0) list.add(index - 1); // 左
    if (col < (cols - 1)) list.add(index + 1); // 右
    return list;
  }

  bool isSameGroup(Piece other) => group != null && group == other.group;
  bool isNeighbour(Piece other) => row == other.row && (col - other.col).abs() == 1 || col == other.col && (row - other.row).abs() == 1;
  bool isCurNeighbour(Piece other) =>
      curRow == other.curRow && (curCol - other.curCol).abs() == 1 || curCol == other.curCol && (curRow - other.curRow).abs() == 1;

  // 以下四个边界检测逻辑：
  // 仅当邻居在当前位置相邻，且它们的原图相对位置正确时，才移除边界（即边界被“吸收”）。
  // 否则，需要绘制边界（群组内部边界或群组外部边界）。

  bool get _hasTopBorder {
    int topCurRow = curRow - 1;
    int topCurCol = curCol;

    // 已经是顶部的宫格, 需要上边框
    if (topCurRow < 0) return true;

    // 获取本碎片当前的上边邻居
    final topPiece = board.getPieceByCoordinate(topCurRow, topCurCol);

    // 邻居缺失（空槽位），需要边框
    if (topPiece == null) {
      _log.warning('找不到 ${toString()} 的上邻居，有错误发生，请检查');
      return true;
    }

    // 如果它们在当前位置相邻，且原图相对位置正确，则边界被内部吸收，不需要绘制。
    // 即：当前碎片(row)在邻居碎片(topPiece.row)的下方一个位置
    if (row == topPiece.row + 1 && col == topPiece.col) {
      return false;
    }

    // 否则，需要绘制边界
    return true;
  }

  // 是否有右边框
  bool get _hasRightBorder {
    int rightCurRow = curRow;
    int rightCurCol = curCol + 1;

    // 已经是最右边的宫格, 需要右边框
    if (rightCurCol >= cols) return true;

    // 获取本碎片当前的右边邻居
    final rightPiece = board.getPieceByCoordinate(rightCurRow, rightCurCol);
    if (rightPiece == null) {
      _log.warning('找不到 ${toString()} 的右邻居，有错误发生，请检查');
      return true;
    }
    // 如果与右邻居的相对位置是正确的，那么没有右边框
    if (row == rightPiece.row && col == rightPiece.col - 1) {
      return false;
    }

    return true;
  }

  // 是否有下边框
  bool get _hasBottomBorder {
    int bottomCurRow = curRow + 1;
    int bottomCurCol = curCol;

    // 已经是底部的宫格, 需要下边框
    if (bottomCurRow >= rows) return true;

    // 获取本碎片当前的底边邻居
    final bottomPiece = board.getPieceByCoordinate(bottomCurRow, bottomCurCol);
    if (bottomPiece == null) {
      _log.warning('找不到 ${toString()} 的下邻居，有错误发生，请检查');
      return true;
    }
    // 如果与下邻居的相对位置是正确的，那么没有下边框
    if (row == bottomPiece.row - 1 && col == bottomPiece.col) {
      return false;
    }

    return true;
  }

  // 是否有左边框
  bool get _hasLeftBorder {
    int leftCurRow = curRow;
    int leftCurCol = curCol - 1;

    // 已经是最左部的宫格, 需要左边框
    if (leftCurCol < 0) return true;

    // 获取本碎片当前的左边邻居
    final leftPiece = board.getPieceByCoordinate(leftCurRow, leftCurCol);
    if (leftPiece == null) {
      _log.warning('找不到 ${toString()} 的左邻居，有错误发生，请检查');
      return true;
    }
    // 如果与左邻居的相对位置是正确的，那么没有左边框
    if (row == leftPiece.row && col == leftPiece.col + 1) {
      return false;
    }

    return true;
  }

  // 生成翻转变换矩阵（结合平移）
  void updateFlipTransform(double flipAngle, vmath.Matrix4 targetTranslate) {
    flipProgress = flipAngle;
    final flipMatrix = vmath.Matrix4.identity()
      ..translate(width / 2, height / 2) // 1. 移到卡片中心（旋转中心）
      ..rotateY(flipAngle) // 2. Y轴旋转（翻转动画）
      ..scale(isFlipped ? -1.0 : 1.0, 1.0, 1.0) // 3. X轴缩放-1：抵消旋转带来的左右镜像
      ..translate(-width / 2, -height / 2); // 4. 移回原位
    transform = targetTranslate * flipMatrix;
  }

  // 判断是否显示正面
  // 当flipProgress在[0, pi/2)（0~90 度）时：卡片未完全翻转，显示背面，isFlipped为false。
  // 当flipProgress在[pi/2, pi]（90~180 度）时：卡片已翻转到正面，isFlipped为true。
  bool get isFlipped => flipProgress >= pi / 2;

  // 生成clip path
  Path _generateClipPath(double w, double h, List<bool> borders, double radius) {
    Path path = Path();

    // 一个角是否为圆角取决于相邻的两条边是否都需要绘制 (即 borders 都为 true)
    final bool tlRounded = borders[0] && borders[3]; // Top && Left
    final bool trRounded = borders[0] && borders[1]; // Top && Right
    final bool brRounded = borders[2] && borders[1]; // Bottom && Right
    final bool blRounded = borders[2] && borders[3]; // Bottom && Left

    // 1. 移动到起点 (左上角，T 边直线段的起点)
    if (tlRounded) {
      path.moveTo(radius, 0);
    } else {
      path.moveTo(0, 0); // 尖角起点
    }
    // A. 顶边 (T) - LineTo
    // 终点是 TR 圆角的起点 (w - radius, 0) 或 TR 尖角 (w, 0)
    if (trRounded) {
      path.lineTo(w - radius, 0);
    } else {
      path.lineTo(w, 0);
    }

    // B. 右上角 (TR) - Arc
    if (trRounded) {
      path.arcToPoint(Offset(w, radius), radius: Radius.circular(radius), clockwise: true);
    }

    // C. 右边 (R) - LineTo
    // 终点是 BR 圆角的起点 (w, h - radius) 或 BR 尖角 (w, h)
    if (brRounded) {
      path.lineTo(w, h - radius);
    } else {
      path.lineTo(w, h);
    }

    // D. 右下角 (BR) - Arc
    if (brRounded) {
      path.arcToPoint(Offset(w - radius, h), radius: Radius.circular(radius), clockwise: true);
    }

    // E. 底边 (B) - LineTo
    // 终点是 BL 圆角的起点 (radius, h) 或 BL 尖角 (0, h)
    if (blRounded) {
      path.lineTo(radius, h);
    } else {
      path.lineTo(0, h);
    }

    // F. 左下角 (BL) - Arc
    if (blRounded) {
      path.arcToPoint(Offset(0, h - radius), radius: Radius.circular(radius), clockwise: true);
    }

    // G. 左边 (L) - LineTo
    // 终点是 TL 圆角的起点 (0, radius) 或 TL 尖角 (0, 0)
    if (tlRounded) {
      path.lineTo(0, radius);
    } else {
      path.lineTo(0, 0);
    }

    // H. 左上角 (TL) - Arc & Close
    if (tlRounded) {
      path.arcToPoint(Offset(radius, 0), radius: Radius.circular(radius), clockwise: true);
    }

    path.close();
    return path;
  }

  // 生成单个碎片边框路径
  Path _generateBorderPath(double w, double h, List<bool> borders, double radius, double offset) {
    Path path = Path();
    final double r = radius;

    // 边界调整后的坐标
    final double x0 = offset; // 左边界
    final double y0 = offset; // 上边界
    final double x1 = w - offset; // 右边界
    final double y1 = h - offset; // 下边界

    // 关键点坐标 (圆角弧的起点/终点)
    final Offset pTL_T = Offset(x0 + r, y0); // Top边起点
    final Offset pTR_T = Offset(x1 - r, y0); // Top边终点
    final Offset pTR_R = Offset(x1, y0 + r); // Right边起点
    final Offset pBR_R = Offset(x1, y1 - r); // Right边终点
    final Offset pBR_B = Offset(x1 - r, y1); // Bottom边起点
    final Offset pBL_B = Offset(x0 + r, y1); // Bottom边终点
    final Offset pBL_L = Offset(x0, y1 - r); // Left边起点
    final Offset pTL_L = Offset(x0, y0 + r); // Left边终点

    // [Top, Right, Bottom, Left]
    final bool hasT = borders[0];
    final bool hasR = borders[1];
    final bool hasB = borders[2];
    final bool hasL = borders[3];

    // --- 1. Top Border (T) ---
    if (hasT) {
      // 检查TL角是否是尖角 (Left边缺失)
      if (hasL) {
        path.moveTo(pTL_T.dx, pTL_T.dy); // 从TL弧的终点开始
      } else {
        // path.moveTo(x0, y0); // 从左上尖角开始
        path.moveTo(0 - offset, y0); // 从左上尖角开始
      }

      // 绘制 Top 直线段
      if (hasR) {
        path.lineTo(pTR_T.dx, pTR_T.dy);
      } else {
        // path.lineTo(x1, y0); // 到右上尖角
        path.lineTo(w + offset, y0); // 到右上尖角
      }
    }

    // --- 2. Top-Right Corner (TR) & Right Border (R) ---
    if (hasT && hasR) {
      // 绘制 TR 弧
      path.arcToPoint(pTR_R, radius: Radius.circular(r), clockwise: true);
    } else if (hasR) {
      // T 缺失，R 存在：需要开始一个新的轮廓，从 TR 弧的起点开始 (pTR_R)
      if (hasT) {
        path.moveTo(pTR_R.dx, pTR_R.dy); // 从 TR 弧终点开始
      } else {
        // path.moveTo(x1, y0); // 从右上尖角开始
        path.moveTo(x1, 0 - offset); // 从右上尖角开始
      }
    }

    if (hasR) {
      // 绘制 Right 直线段
      if (hasB) {
        path.lineTo(pBR_R.dx, pBR_R.dy);
      } else {
        // path.lineTo(x1, y1); // 到右下尖角
        path.lineTo(x1, h + offset); // 到右下尖角
      }
    }

    // --- 3. Bottom-Right Corner (BR) & Bottom Border (B) ---
    if (hasR && hasB) {
      // 绘制 BR 弧
      path.arcToPoint(pBR_B, radius: Radius.circular(r), clockwise: true);
    } else if (hasB) {
      // R 缺失，B 存在：需要开始一个新的轮廓，从 BR 弧的起点开始 (pBR_B)
      if (hasR) {
        path.moveTo(pBR_B.dx, pBR_B.dy); // 从 BR 弧终点开始
      } else {
        // path.moveTo(x1, y1); // 从右下尖角开始
        path.moveTo(w + offset, y1); // 从右下尖角开始
      }
    }

    if (hasB) {
      // 绘制 Bottom 直线段
      if (hasL) {
        path.lineTo(pBL_B.dx, pBL_B.dy);
      } else {
        // path.lineTo(x0, y1); // 到左下尖角
        path.lineTo(0 - offset, y1); // 到左下尖角
      }
    }

    // --- 4. Bottom-Left Corner (BL) & Left Border (L) ---
    if (hasB && hasL) {
      // 绘制 BL 弧
      path.arcToPoint(pBL_L, radius: Radius.circular(r), clockwise: true);
    } else if (hasL) {
      // B 缺失，L 存在：需要开始一个新的轮廓，从 BL 弧的起点开始 (pBL_L)
      if (hasB) {
        path.moveTo(pBL_L.dx, pBL_L.dy); // 从 BL 弧终点开始
      } else {
        // path.moveTo(x0, y1); // 从左下尖角开始
        path.moveTo(x0, h + offset); // 从左下尖角开始
      }
    }

    if (hasL) {
      // 绘制 Left 直线段
      if (hasT) {
        path.lineTo(pTL_L.dx, pTL_L.dy);
      } else {
        // path.lineTo(x0, y0); // 到左上尖角
        path.lineTo(x0, 0 - offset); // 到左上尖角
      }
    }

    // --- 5. Top-Left Corner (TL) ---
    if (hasL && hasT) {
      // 绘制 TL 弧，连接回 Top Border 的起点
      path.arcToPoint(pTL_T, radius: Radius.circular(r), clockwise: true);
    }
    // 注意: 不调用 path.close()，保持路径开放。

    return path;
  }

  // 生成碎片的clipPath, innerLinePath, outLinePath
  // 增加 forceRecalculate 参数，用于群组路径计算时强制重新计算边界状态
  List<Path> generatePaths({bool forceRecalculate = false}) {
    // 如果没有 group 且路径已缓存，则直接返回
    if (group == null && !forceRecalculate && path != null && outLinePath != null && innerLinePath != null) {
      return [path!, outLinePath!, innerLinePath!];
    }

    // 先确定4条边的状态[Top, Right, Bottom, Left]
    List<bool> borders = [true, true, true, true];

    // 如果是单个碎片，4条边都需要，只有 piece 在 group 中才需要判断
    if (group != null || forceRecalculate) {
      // 即使是单独计算，也需要实时检查
      borders = [_hasTopBorder, _hasRightBorder, _hasBottomBorder, _hasLeftBorder];
    }

    path = _generateClipPath(width, height, borders, board.cornerRadius);
    outLinePath = _generateBorderPath(width, height, borders, board.cornerRadius, _outLineOffset);
    innerLinePath = _generateBorderPath(width, height, borders, board.cornerRadius, _innerLineOffset);

    return [path!, outLinePath!, innerLinePath!];
  }
}
