"""
坐标转换工具模块

提供各种坐标转换功能：
- 底层坐标计算（IoU、重叠比例）
- 多边形/bbox 格式转换
- 相对坐标 → 绝对坐标转换
- OCR 格式转换
- 旋转坐标逆变换
- HTML data-bbox 坐标转换
"""
import re
import json
from typing import Dict, List, Any, Optional, Tuple, Union
import numpy as np
from loguru import logger

# 导入 merger 组件
try:
    from merger import BBoxExtractor
    MERGER_AVAILABLE = True
except ImportError:
    MERGER_AVAILABLE = False
    BBoxExtractor = None

# 导入 MinerU 组件（用于 IoU 计算）
try:
    from mineru.utils.boxbase import calculate_iou as mineru_calculate_iou
    from mineru.utils.boxbase import calculate_overlap_area_2_minbox_area_ratio
    MINERU_BOXBASE_AVAILABLE = True
except ImportError:
    MINERU_BOXBASE_AVAILABLE = False
    mineru_calculate_iou = None
    calculate_overlap_area_2_minbox_area_ratio = None


class CoordinateUtils:
    """坐标转换工具类"""
    
    # ==================== 底层坐标计算方法 ====================
    
    @staticmethod
    def calculate_iou(bbox1: List[float], bbox2: List[float]) -> float:
        """
        计算两个 bbox 的 IoU（交并比）
        
        Args:
            bbox1: 第一个 bbox [x1, y1, x2, y2]
            bbox2: 第二个 bbox [x1, y1, x2, y2]
            
        Returns:
            IoU 值
        """
        if MINERU_BOXBASE_AVAILABLE and mineru_calculate_iou is not None:
            return mineru_calculate_iou(bbox1, bbox2)
        
        # 备用实现
        x_left = max(bbox1[0], bbox2[0])
        y_top = max(bbox1[1], bbox2[1])
        x_right = min(bbox1[2], bbox2[2])
        y_bottom = min(bbox1[3], bbox2[3])
        
        if x_right < x_left or y_bottom < y_top:
            return 0.0
        
        intersection_area = (x_right - x_left) * (y_bottom - y_top)
        bbox1_area = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
        bbox2_area = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])
        
        if bbox1_area == 0 or bbox2_area == 0:
            return 0.0
        
        return intersection_area / float(bbox1_area + bbox2_area - intersection_area)
    
    @staticmethod
    def calculate_overlap_ratio(bbox1: List[float], bbox2: List[float]) -> float:
        """
        计算重叠面积占小框面积的比例
        
        Args:
            bbox1: 第一个 bbox [x1, y1, x2, y2]
            bbox2: 第二个 bbox [x1, y1, x2, y2]
            
        Returns:
            重叠比例
        """
        if MINERU_BOXBASE_AVAILABLE and calculate_overlap_area_2_minbox_area_ratio is not None:
            return calculate_overlap_area_2_minbox_area_ratio(bbox1, bbox2)
        
        # 备用实现
        x_left = max(bbox1[0], bbox2[0])
        y_top = max(bbox1[1], bbox2[1])
        x_right = min(bbox1[2], bbox2[2])
        y_bottom = min(bbox1[3], bbox2[3])
        
        if x_right < x_left or y_bottom < y_top:
            return 0.0
        
        intersection_area = (x_right - x_left) * (y_bottom - y_top)
        area1 = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
        area2 = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])
        min_area = min(area1, area2)
        
        if min_area == 0:
            return 0.0
        
        return intersection_area / min_area
    
    @staticmethod
    def calculate_overlap_in_bbox1_ratio(
        bbox1: List[float], 
        bbox2: List[float]
    ) -> float:
        """
        计算 bbox1 被 bbox2 覆盖的面积比例
        
        Args:
            bbox1: 第一个 bbox [x1, y1, x2, y2]
            bbox2: 第二个 bbox [x1, y1, x2, y2]
            
        Returns:
            bbox1 被覆盖的比例
        """
        x_left = max(bbox1[0], bbox2[0])
        y_top = max(bbox1[1], bbox2[1])
        x_right = min(bbox1[2], bbox2[2])
        y_bottom = min(bbox1[3], bbox2[3])
        
        if x_right < x_left or y_bottom < y_top:
            return 0.0
        
        intersection_area = (x_right - x_left) * (y_bottom - y_top)
        bbox1_area = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
        
        if bbox1_area == 0:
            return 0.0
        
        return intersection_area / bbox1_area
    
    @staticmethod
    def poly_to_bbox(poly: Union[List, None]) -> List[float]:
        """
        将多边形坐标转换为 bbox 格式
        
        Args:
            poly: 多边形坐标，支持以下格式：
                - [[x1,y1], [x2,y1], [x2,y2], [x1,y2]] (4个点)
                - [x1, y1, x2, y1, x2, y2, x1, y2] (8个值)
                - [x1, y1, x2, y2] (4个值，已是bbox)
                
        Returns:
            bbox [x1, y1, x2, y2]
        """
        if not poly:
            return [0, 0, 0, 0]
        
        # 处理嵌套列表格式 [[x1,y1], [x2,y1], ...]
        if isinstance(poly[0], (list, tuple)):
            xs = [p[0] for p in poly]
            ys = [p[1] for p in poly]
            return [min(xs), min(ys), max(xs), max(ys)]
        
        # 处理平面列表格式
        if len(poly) == 4:
            # 已经是 bbox 格式
            return list(poly)
        elif len(poly) >= 8:
            # 8点格式：[x1, y1, x2, y1, x2, y2, x1, y2]
            xs = [poly[i] for i in range(0, len(poly), 2)]
            ys = [poly[i] for i in range(1, len(poly), 2)]
            return [min(xs), min(ys), max(xs), max(ys)]
        
        return [0, 0, 0, 0]
    
    @staticmethod
    def bbox_to_poly(bbox: List[float]) -> List[List[float]]:
        """
        将 bbox 转换为多边形坐标
        
        Args:
            bbox: [x1, y1, x2, y2]
            
        Returns:
            [[x1,y1], [x2,y1], [x2,y2], [x1,y2]]
        """
        if not bbox or len(bbox) < 4:
            return [[0, 0], [0, 0], [0, 0], [0, 0]]
        
        x1, y1, x2, y2 = bbox[:4]
        return [
            [float(x1), float(y1)],
            [float(x2), float(y1)],
            [float(x2), float(y2)],
            [float(x1), float(y2)]
        ]
    
    # ==================== 图像裁剪 ====================
    
    @staticmethod
    def crop_region(image: np.ndarray, bbox: List[float]) -> np.ndarray:
        """
        裁剪图像区域
        
        Args:
            image: 原始图像
            bbox: 裁剪区域 [x1, y1, x2, y2]
            
        Returns:
            裁剪后的图像
        """
        if len(bbox) < 4:
            return image
        
        x1, y1, x2, y2 = map(int, bbox[:4])
        h, w = image.shape[:2]
        
        x1 = max(0, min(x1, w))
        y1 = max(0, min(y1, h))
        x2 = max(x1, min(x2, w))
        y2 = max(y1, min(y2, h))
        
        return image[y1:y2, x1:x2]
    
    @staticmethod
    def bbox_overlap(bbox1: List[float], bbox2: List[float]) -> bool:
        """
        检查两个 bbox 是否重叠
        
        Args:
            bbox1: 第一个 bbox [x1, y1, x2, y2]
            bbox2: 第二个 bbox [x1, y1, x2, y2]
            
        Returns:
            是否重叠
        """
        if len(bbox1) < 4 or len(bbox2) < 4:
            return False
        
        x1_1, y1_1, x2_1, y2_1 = bbox1[:4]
        x1_2, y1_2, x2_2, y2_2 = bbox2[:4]
        
        if x2_1 < x1_2 or x2_2 < x1_1:
            return False
        if y2_1 < y1_2 or y2_2 < y1_1:
            return False
        
        return True
    
    @staticmethod
    def convert_to_absolute_coords(
        relative_bbox: List, 
        region_bbox: List[float]
    ) -> List:
        """
        将相对坐标转换为绝对坐标
        
        Args:
            relative_bbox: 相对坐标
            region_bbox: 区域的绝对坐标 [x1, y1, x2, y2]
            
        Returns:
            绝对坐标
        """
        if not relative_bbox or len(region_bbox) < 4:
            return relative_bbox
        
        bx1, by1 = region_bbox[0], region_bbox[1]
        
        # 处理4点坐标格式 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
        if isinstance(relative_bbox[0], (list, tuple)):
            return [
                [p[0] + bx1, p[1] + by1] for p in relative_bbox
            ]
        
        # 处理4值坐标格式 [x1, y1, x2, y2]
        if len(relative_bbox) >= 4:
            return [
                relative_bbox[0] + bx1,
                relative_bbox[1] + by1,
                relative_bbox[2] + bx1,
                relative_bbox[3] + by1
            ]
        
        return relative_bbox
    
    @staticmethod
    def convert_ocr_to_matcher_format(
        ocr_poly: List,
        text: str,
        confidence: float,
        idx: int,
        table_bbox: Optional[List[float]] = None
    ) -> Optional[Dict[str, Any]]:
        """
        将 OCR 结果转换为 TableCellMatcher 期望的格式
        
        OCR 返回格式:
            bbox: [[x1,y1], [x2,y1], [x2,y2], [x1,y2]]  # 4点多边形
            text: str
            confidence: float
        
        TableCellMatcher 期望格式:
            text: str
            bbox: [x_min, y_min, x_max, y_max]  # 4值矩形
            poly: [[x1,y1], [x2,y1], [x2,y2], [x1,y2]]  # 4点多边形
            score: float
            paddle_bbox_index: int
            used: bool
        
        Args:
            ocr_poly: OCR返回的4点多边形坐标
            text: 识别文本
            confidence: 置信度
            idx: 索引
            table_bbox: 表格的绝对坐标，用于转换相对坐标
        
        Returns:
            转换后的字典，或 None（如果无效）
        """
        if not ocr_poly or not text:
            return None
        
        poly = []
        
        # 格式1: [[x1,y1], [x2,y2], [x3,y3], [x4,y4]] - 4点多边形
        if isinstance(ocr_poly[0], (list, tuple)) and len(ocr_poly) == 4:
            poly = [[float(p[0]), float(p[1])] for p in ocr_poly]
        
        # 格式2: [x1, y1, x2, y1, x2, y2, x1, y2] - 8值展平格式
        elif len(ocr_poly) == 8 and isinstance(ocr_poly[0], (int, float)):
            poly = [
                [float(ocr_poly[0]), float(ocr_poly[1])],
                [float(ocr_poly[2]), float(ocr_poly[3])],
                [float(ocr_poly[4]), float(ocr_poly[5])],
                [float(ocr_poly[6]), float(ocr_poly[7])]
            ]
        
        # 格式3: [x1, y1, x2, y2] - 4值矩形格式
        elif len(ocr_poly) == 4 and isinstance(ocr_poly[0], (int, float)):
            x1, y1, x2, y2 = ocr_poly
            poly = [
                [float(x1), float(y1)],
                [float(x2), float(y1)],
                [float(x2), float(y2)],
                [float(x1), float(y2)]
            ]
        else:
            logger.warning(f"Unknown OCR bbox format: {ocr_poly}")
            return None
        
        # 转换为绝对坐标（相对于整页图片）
        if table_bbox and len(table_bbox) >= 2:
            offset_x, offset_y = table_bbox[0], table_bbox[1]
            poly = [[p[0] + offset_x, p[1] + offset_y] for p in poly]
        
        # 从多边形计算 bbox [x_min, y_min, x_max, y_max]
        xs = [p[0] for p in poly]
        ys = [p[1] for p in poly]
        bbox = [min(xs), min(ys), max(xs), max(ys)]
        
        return {
            'text': text,
            'bbox': bbox,
            'poly': poly,
            'score': confidence,
            'paddle_bbox_index': idx,
            'used': False
        }
    
    @staticmethod
    def inverse_rotate_table_coords(
        cells: List[Dict],
        html: str,
        rotation_angle: float,
        orig_table_size: Tuple[int, int],
        table_bbox: List[float]
    ) -> Tuple[List[Dict], str]:
        """
        将旋转后的坐标逆向转换回原图坐标
        
        Args:
            cells: 单元格列表（坐标是旋转后的）
            html: HTML字符串（data-bbox是旋转后的）
            rotation_angle: 旋转角度
            orig_table_size: 原始表格尺寸 (width, height)
            table_bbox: 表格在整页图片中的位置 [x1, y1, x2, y2]
        
        Returns:
            (转换后的cells, 转换后的html)
        """
        if not MERGER_AVAILABLE or BBoxExtractor is None:
            # 如果 merger 不可用，只添加偏移量
            converted_cells = CoordinateUtils.add_table_offset_to_cells(cells, table_bbox)
            converted_html = CoordinateUtils.add_table_offset_to_html(html, table_bbox)
            return converted_cells, converted_html
        
        table_offset_x, table_offset_y = table_bbox[0], table_bbox[1]
        
        # 转换 cells 中的 bbox
        converted_cells = []
        for cell in cells:
            cell_copy = cell.copy()
            cell_bbox = cell.get('bbox', [])
            if cell_bbox and len(cell_bbox) == 4:
                # 先逆向旋转，再加上表格偏移量
                orig_bbox = BBoxExtractor.inverse_rotate_box_coordinates(
                    cell_bbox, rotation_angle, orig_table_size
                )
                # 加上表格偏移量转换为整页坐标
                cell_copy['bbox'] = [
                    orig_bbox[0] + table_offset_x,
                    orig_bbox[1] + table_offset_y,
                    orig_bbox[2] + table_offset_x,
                    orig_bbox[3] + table_offset_y
                ]
            converted_cells.append(cell_copy)
        
        # 转换 HTML 中的 data-bbox
        def replace_bbox(match):
            bbox_str = match.group(1)
            try:
                bbox = json.loads(bbox_str)
                if len(bbox) == 4:
                    orig_bbox = BBoxExtractor.inverse_rotate_box_coordinates(
                        bbox, rotation_angle, orig_table_size
                    )
                    new_bbox = [
                        orig_bbox[0] + table_offset_x,
                        orig_bbox[1] + table_offset_y,
                        orig_bbox[2] + table_offset_x,
                        orig_bbox[3] + table_offset_y
                    ]
                    return f'data-bbox="[{new_bbox[0]},{new_bbox[1]},{new_bbox[2]},{new_bbox[3]}]"'
            except:
                pass
            return match.group(0)
        
        converted_html = re.sub(
            r'data-bbox="\[([^\]]+)\]"',
            replace_bbox,
            html
        )
        
        return converted_cells, converted_html
    
    @staticmethod
    def add_table_offset_to_cells(
        cells: List[Dict],
        table_bbox: List[float]
    ) -> List[Dict]:
        """
        为单元格坐标添加表格偏移量（无旋转情况）
        
        Args:
            cells: 单元格列表
            table_bbox: 表格位置 [x1, y1, x2, y2]
        
        Returns:
            转换后的 cells
        """
        offset_x, offset_y = table_bbox[0], table_bbox[1]
        
        converted_cells = []
        for cell in cells:
            cell_copy = cell.copy()
            cell_bbox = cell.get('bbox', [])
            if cell_bbox and len(cell_bbox) == 4:
                cell_copy['bbox'] = [
                    cell_bbox[0] + offset_x,
                    cell_bbox[1] + offset_y,
                    cell_bbox[2] + offset_x,
                    cell_bbox[3] + offset_y
                ]
            converted_cells.append(cell_copy)
        
        return converted_cells
    
    @staticmethod
    def add_table_offset_to_html(
        html: str,
        table_bbox: List[float]
    ) -> str:
        """
        为HTML中的data-bbox添加表格偏移量（无旋转情况）
        
        Args:
            html: HTML字符串
            table_bbox: 表格位置 [x1, y1, x2, y2]
        
        Returns:
            转换后的 HTML
        """
        offset_x, offset_y = table_bbox[0], table_bbox[1]
        
        def replace_bbox(match):
            bbox_str = match.group(1)
            try:
                bbox = json.loads(f"[{bbox_str}]")
                if len(bbox) == 4:
                    new_bbox = [
                        bbox[0] + offset_x,
                        bbox[1] + offset_y,
                        bbox[2] + offset_x,
                        bbox[3] + offset_y
                    ]
                    return f'data-bbox="[{new_bbox[0]},{new_bbox[1]},{new_bbox[2]},{new_bbox[3]}]"'
            except:
                pass
            return match.group(0)
        
        converted_html = re.sub(
            r'data-bbox="\[([^\]]+)\]"',
            replace_bbox,
            html
        )
        
        return converted_html
    
    @staticmethod
    def add_table_offset_to_ocr_boxes(
        ocr_boxes: List[Dict],
        table_bbox: List[float]
    ) -> List[Dict]:
        """
        为 ocr_boxes 添加表格偏移量，将相对坐标转换为页面绝对坐标
        
        Args:
            ocr_boxes: OCR 框列表
            table_bbox: 表格在页面中的位置 [x1, y1, x2, y2]
            
        Returns:
            转换后的 ocr_boxes
        """
        if not ocr_boxes or not table_bbox:
            return ocr_boxes
        
        offset_x = table_bbox[0]
        offset_y = table_bbox[1]
        
        converted_boxes = []
        for box in ocr_boxes:
            new_box = box.copy()
            
            # 转换 bbox [x1, y1, x2, y2]
            if 'bbox' in new_box and new_box['bbox']:
                bbox = new_box['bbox']
                if len(bbox) >= 4:
                    new_box['bbox'] = [
                        bbox[0] + offset_x,
                        bbox[1] + offset_y,
                        bbox[2] + offset_x,
                        bbox[3] + offset_y
                    ]
            
            # 转换 poly [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
            if 'poly' in new_box and new_box['poly']:
                poly = new_box['poly']
                new_poly = []
                for point in poly:
                    if isinstance(point, (list, tuple)) and len(point) >= 2:
                        new_poly.append([point[0] + offset_x, point[1] + offset_y])
                    else:
                        new_poly.append(point)
                new_box['poly'] = new_poly
            
            converted_boxes.append(new_box)
        
        return converted_boxes
    
    @staticmethod
    def inverse_rotate_ocr_boxes(
        ocr_boxes: List[Dict],
        rotation_angle: float,
        orig_table_size: Tuple[int, int],
        table_bbox: List[float]
    ) -> List[Dict]:
        """
        对 ocr_boxes 进行逆向旋转并添加表格偏移量
        
        Args:
            ocr_boxes: OCR 框列表
            rotation_angle: 表格旋转角度
            orig_table_size: 原始表格尺寸 (width, height)
            table_bbox: 表格在页面中的位置
            
        Returns:
            转换后的 ocr_boxes
        """
        if not ocr_boxes:
            return ocr_boxes
        
        if not MERGER_AVAILABLE or BBoxExtractor is None:
            return CoordinateUtils.add_table_offset_to_ocr_boxes(ocr_boxes, table_bbox)
        
        offset_x = table_bbox[0]
        offset_y = table_bbox[1]
        
        converted_boxes = []
        for box in ocr_boxes:
            new_box = box.copy()
            
            # 逆向旋转 bbox
            if 'bbox' in new_box and new_box['bbox']:
                bbox = new_box['bbox']
                if len(bbox) >= 4:
                    try:
                        rotated_bbox = BBoxExtractor.inverse_rotate_box_coordinates(
                            bbox, rotation_angle, orig_table_size
                        )
                        new_box['bbox'] = [
                            rotated_bbox[0] + offset_x,
                            rotated_bbox[1] + offset_y,
                            rotated_bbox[2] + offset_x,
                            rotated_bbox[3] + offset_y
                        ]
                    except Exception as e:
                        logger.debug(f"Failed to inverse rotate ocr_box bbox: {e}")
            
            # 逆向旋转 poly
            if 'poly' in new_box and new_box['poly']:
                poly = new_box['poly']
                try:
                    poly_list = [[float(p[0]), float(p[1])] for p in poly]
                    rotated_poly = BBoxExtractor.inverse_rotate_coordinates(
                        poly_list, rotation_angle, orig_table_size
                    )
                    new_poly = []
                    for point in rotated_poly:
                        new_poly.append([point[0] + offset_x, point[1] + offset_y])
                    new_box['poly'] = new_poly
                except Exception as e:
                    logger.debug(f"Failed to inverse rotate ocr_box poly: {e}")
            
            converted_boxes.append(new_box)
        
        return converted_boxes
    
    @staticmethod
    def is_poly_format(bbox: Any) -> bool:
        """
        检测 bbox 是否为四点多边形格式
        
        四点格式: [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
        矩形格式: [x_min, y_min, x_max, y_max]
        """
        if not bbox or not isinstance(bbox, list):
            return False
        if len(bbox) != 4:
            return False
        return isinstance(bbox[0], (list, tuple))
    
    @staticmethod
    def transform_coords_to_original(
        element: Dict[str, Any],
        rotate_angle: int,
        rotated_shape: Tuple,
        original_shape: Tuple
    ) -> Dict[str, Any]:
        """
        将坐标从旋转后的图片坐标系转换回原始图片坐标系
        
        Args:
            element: 元素字典(包含bbox等坐标信息)
            rotate_angle: 旋转角度(0, 90, 180, 270)
            rotated_shape: 旋转后图片的shape (h, w, c)
            original_shape: 原始图片的shape (h, w, c)
        
        Returns:
            坐标转换后的元素字典(深拷贝)
        """
        import copy
        element = copy.deepcopy(element)
        
        if rotate_angle == 0 or not MERGER_AVAILABLE or BBoxExtractor is None:
            return element
        
        # 原始图片尺寸 (width, height)
        orig_h, orig_w = original_shape[:2]
        orig_image_size = (orig_w, orig_h)
        
        # 转换主bbox
        if 'bbox' in element and element['bbox']:
            element['bbox'] = BBoxExtractor.inverse_rotate_box_coordinates(
                element['bbox'], rotate_angle, orig_image_size
            )
        
        # 转换表格相关坐标
        if element.get('type') == 'table' and 'content' in element:
            content = element['content']
            
            # 转换 OCR boxes
            if 'ocr_boxes' in content and content['ocr_boxes']:
                for box in content['ocr_boxes']:
                    if 'bbox' in box and box['bbox']:
                        box['bbox'] = BBoxExtractor.inverse_rotate_box_coordinates(
                            box['bbox'], rotate_angle, orig_image_size
                        )
            
            # 转换 cells
            if 'cells' in content and content['cells']:
                for cell in content['cells']:
                    if 'bbox' in cell and cell['bbox']:
                        cell['bbox'] = BBoxExtractor.inverse_rotate_box_coordinates(
                            cell.get('bbox', []), rotate_angle, orig_image_size
                        )
            
            # 转换 HTML 中的 data-bbox 属性
            if 'html' in content and content['html']:
                content['html'] = CoordinateUtils.transform_html_data_bbox(
                    content['html'], rotate_angle, orig_image_size
                )
        
        # 转换文本OCR details
        if 'content' in element and 'ocr_details' in element.get('content', {}):
            ocr_details = element['content'].get('ocr_details', [])
            if ocr_details:
                for detail in ocr_details:
                    if 'bbox' in detail and detail['bbox']:
                        if CoordinateUtils.is_poly_format(detail['bbox']):
                            detail['bbox'] = BBoxExtractor.inverse_rotate_coordinates(
                                detail['bbox'], rotate_angle, orig_image_size
                            )
                        else:
                            detail['bbox'] = BBoxExtractor.inverse_rotate_box_coordinates(
                                detail.get('bbox', []), rotate_angle, orig_image_size
                            )
        
        return element
    
    @staticmethod
    def transform_html_data_bbox(
        html: str,
        rotate_angle: int,
        orig_image_size: Tuple[int, int]
    ) -> str:
        """
        转换 HTML 中所有 data-bbox 属性的坐标
        
        Args:
            html: 包含 data-bbox 属性的 HTML 字符串
            rotate_angle: 旋转角度
            orig_image_size: 原始图片尺寸 (width, height)
        
        Returns:
            转换后的 HTML 字符串
        """
        if not MERGER_AVAILABLE or BBoxExtractor is None:
            return html
        
        def replace_bbox(match):
            try:
                bbox_str = match.group(1)
                bbox = json.loads(bbox_str)
                if bbox and len(bbox) == 4:
                    transformed = BBoxExtractor.inverse_rotate_box_coordinates(
                        bbox, rotate_angle, orig_image_size
                    )
                    return f'data-bbox="{json.dumps(transformed)}"'
            except (json.JSONDecodeError, ValueError):
                pass
            return match.group(0)
        
        pattern = r'data-bbox="(\[[^\]]+\])"'
        return re.sub(pattern, replace_bbox, html)

