"""
HWPX 스타일 주입기
pyhwpx로 생성된 HWPX 파일에 커스텀 스타일을 후처리로 주입

워크플로우:
1. HWPX 압축 해제
2. header.xml에 커스텀 스타일 정의 추가
3. section*.xml에서 역할별 styleIDRef 매핑
4. 다시 압축
"""

import os
import re
import zipfile
import shutil
import tempfile
from pathlib import Path
from typing import Dict, List, Optional
from dataclasses import dataclass


@dataclass
class StyleDefinition:
    """스타일 정의"""
    id: int
    name: str
    font_size: int      # hwpunit (pt * 100)
    font_bold: bool
    font_color: str     # #RRGGBB
    align: str          # LEFT, CENTER, RIGHT, JUSTIFY
    line_spacing: int   # percent (160 = 160%)
    indent_left: int    # hwpunit
    indent_first: int   # hwpunit
    space_before: int   # hwpunit
    space_after: int    # hwpunit
    outline_level: int = -1  # 🆕 개요 수준 (-1=없음, 0=1수준, 1=2수준, ...)


# 역할 → 스타일 정의 매핑
ROLE_STYLES: Dict[str, StyleDefinition] = {
    # 🆕 개요 문단 (자동 번호 매기기!)
    'H1': StyleDefinition(
        id=101, name='제1장 제목', font_size=2200, font_bold=True,
        font_color='#006400', align='CENTER', line_spacing=200,
        indent_left=0, indent_first=0, space_before=400, space_after=200,
        outline_level=0  # 🆕 제^1장
    ),
    'H2': StyleDefinition(
        id=102, name='1.1 제목', font_size=1500, font_bold=True,
        font_color='#03581d', align='LEFT', line_spacing=200,
        indent_left=0, indent_first=0, space_before=300, space_after=100,
        outline_level=1  # 🆕 ^1.^2
    ),
    'H3': StyleDefinition(
        id=103, name='1.1.1 제목', font_size=1400, font_bold=True,
        font_color='#228B22', align='LEFT', line_spacing=200,
        indent_left=500, indent_first=0, space_before=200, space_after=100,
        outline_level=2  # 🆕 ^1.^2.^3
    ),
    'H4': StyleDefinition(
        id=104, name='가. 제목', font_size=1300, font_bold=True,
        font_color='#000000', align='LEFT', line_spacing=200,
        indent_left=1000, indent_first=0, space_before=150, space_after=50,
        outline_level=3  # 🆕 ^4.
    ),
    'H5': StyleDefinition(
        id=105, name='1) 제목', font_size=1200, font_bold=True,
        font_color='#000000', align='LEFT', line_spacing=200,
        indent_left=1500, indent_first=0, space_before=100, space_after=50,
        outline_level=4  # 🆕 ^5)
    ),
    'H6': StyleDefinition(
        id=106, name='가) 제목', font_size=1150, font_bold=True,
        font_color='#000000', align='LEFT', line_spacing=200,
        indent_left=2000, indent_first=0, space_before=100, space_after=50,
        outline_level=5  # 🆕 ^6)
    ),
    'H7': StyleDefinition(
        id=115, name='① 제목', font_size=1100, font_bold=True,
        font_color='#000000', align='LEFT', line_spacing=200,
        indent_left=2300, indent_first=0, space_before=100, space_after=50,
        outline_level=6  # 🆕 ^7 (원문자)
    ),
    # 본문 스타일 (개요 아님)
    'BODY': StyleDefinition(
        id=107, name='○본문', font_size=1100, font_bold=False,
        font_color='#000000', align='JUSTIFY', line_spacing=200,
        indent_left=1500, indent_first=0, space_before=0, space_after=0
    ),
    'LIST_ITEM': StyleDefinition(
        id=108, name='●본문', font_size=1050, font_bold=False,
        font_color='#000000', align='JUSTIFY', line_spacing=200,
        indent_left=2500, indent_first=0, space_before=0, space_after=0
    ),
    'TABLE_CAPTION': StyleDefinition(
        id=109, name='<표 제목>', font_size=1100, font_bold=True,
        font_color='#000000', align='LEFT', line_spacing=130,
        indent_left=0, indent_first=0, space_before=200, space_after=100
    ),
    'FIGURE_CAPTION': StyleDefinition(
        id=110, name='<그림 제목>', font_size=1100, font_bold=True,
        font_color='#000000', align='CENTER', line_spacing=130,
        indent_left=0, indent_first=0, space_before=100, space_after=200
    ),
    'COVER_TITLE': StyleDefinition(
        id=111, name='표지제목', font_size=2800, font_bold=True,
        font_color='#1a365d', align='CENTER', line_spacing=150,
        indent_left=0, indent_first=0, space_before=0, space_after=200
    ),
    'COVER_SUBTITLE': StyleDefinition(
        id=112, name='표지부제', font_size=1800, font_bold=False,
        font_color='#2d3748', align='CENTER', line_spacing=150,
        indent_left=0, indent_first=0, space_before=0, space_after=100
    ),
    'TOC_1': StyleDefinition(
        id=113, name='목차1수준', font_size=1200, font_bold=True,
        font_color='#000000', align='LEFT', line_spacing=180,
        indent_left=0, indent_first=0, space_before=100, space_after=50
    ),
    'TOC_2': StyleDefinition(
        id=114, name='목차2수준', font_size=1100, font_bold=False,
        font_color='#000000', align='LEFT', line_spacing=180,
        indent_left=500, indent_first=0, space_before=0, space_after=0
    ),
}

# ⚠️ 개요 자동 번호 기능 활성화!
# idRef="0"은 numbering id=1을 참조하므로, 해당 패턴을 교체하면 동작함


class HwpxStyleInjector:
    """HWPX 스타일 주입기"""
    
    def __init__(self):
        self.temp_dir: Optional[Path] = None
        self.role_to_style_id: Dict[str, int] = {}
        self.role_to_para_id: Dict[str, int] = {}   # 🆕
        self.role_to_char_id: Dict[str, int] = {}   # 🆕
        self.next_char_id = 0
        self.next_para_id = 0
        self.next_style_id = 0
    
    def _find_max_ids(self):
        """기존 스타일 교체: 바탕글(id=0)만 유지, 나머지는 우리 스타일로 교체"""
        header_path = self.temp_dir / "Contents" / "header.xml"
        if not header_path.exists():
            self.next_char_id = 1
            self.next_para_id = 1
            self.next_style_id = 1
            return
        
        content = header_path.read_text(encoding='utf-8')
        
        # 🆕 기존 "본문", "개요 1~10" 등 스타일 제거 (id=1~22)
        # 바탕글(id=0)만 유지!
        
        # style id=1~30 제거 (바탕글 제외)
        content = re.sub(r'<hh:style id="([1-9]|[12]\d|30)"[^/]*/>\s*', '', content)
        
        # itemCnt는 나중에 _update_item_counts에서 자동 업데이트됨
        
        # 파일 저장
        header_path.write_text(content, encoding='utf-8')
        print(f"   [INFO] 기존 스타일(본문, 개요1~10 등) 제거 완료")
        
        # charPr, paraPr은 기존 것 다음부터 (참조 깨지지 않도록)
        char_ids = [int(m) for m in re.findall(r'<hh:charPr id="(\d+)"', content)]
        self.next_char_id = max(char_ids) + 1 if char_ids else 20
        
        para_ids = [int(m) for m in re.findall(r'<hh:paraPr id="(\d+)"', content)]
        self.next_para_id = max(para_ids) + 1 if para_ids else 20
        
        # 스타일은 1부터 시작! (Ctrl+2 = id=1, Ctrl+3 = id=2, ...)
        self.next_style_id = 1
    
    def inject(self, hwpx_path: str, role_positions: Dict[str, List[tuple]]) -> str:
        """
        HWPX 파일에 커스텀 스타일 주입
        
        Args:
            hwpx_path: 원본 HWPX 파일 경로
            role_positions: 역할별 위치 정보 {role: [(section_idx, para_idx), ...]}
        
        Returns:
            수정된 HWPX 파일 경로
        """
        print(f"\n🎨 HWPX 스타일 주입 시작...")
        print(f"   입력: {hwpx_path}")
        
        # 1. 임시 디렉토리에 압축 해제
        self.temp_dir = Path(tempfile.mkdtemp(prefix='hwpx_inject_'))
        print(f"   임시 폴더: {self.temp_dir}")
        
        try:
            with zipfile.ZipFile(hwpx_path, 'r') as zf:
                zf.extractall(self.temp_dir)
            
            # 압축 해제 직후 section 파일 크기 확인
            print(f"   [DEBUG] After unzip:")
            for sec in ['section0.xml', 'section1.xml', 'section2.xml']:
                sec_path = self.temp_dir / "Contents" / sec
                if sec_path.exists():
                    print(f"   [DEBUG] {sec} size: {sec_path.stat().st_size} bytes")
            
            # 🆕 기존 최대 ID 찾기 (연속 ID 할당을 위해)
            self._find_max_ids()
            print(f"   [DEBUG] Starting IDs: char={self.next_char_id}, para={self.next_para_id}, style={self.next_style_id}")
            
            # 2. header.xml에 스타일 정의 추가
            used_roles = set(role_positions.keys())
            self._inject_header_styles(used_roles)
            
            # 3. section*.xml에 styleIDRef 매핑
            self._inject_section_styles(role_positions)
            
            # 4. 다시 압축
            output_path = hwpx_path  # 원본 덮어쓰기
            self._repack_hwpx(output_path)
            
            print(f"   ✅ 스타일 주입 완료: {output_path}")
            return output_path
            
        finally:
            # 임시 폴더 정리
            if self.temp_dir and self.temp_dir.exists():
                shutil.rmtree(self.temp_dir)
    
    def _inject_header_styles(self, used_roles: set):
        """header.xml에 스타일 정의 추가 (모든 ROLE_STYLES 주입)"""
        header_path = self.temp_dir / "Contents" / "header.xml"
        if not header_path.exists():
            print("   [경고] header.xml 없음")
            return
        
        content = header_path.read_text(encoding='utf-8')
        
        # 🆕 모든 ROLE_STYLES 주입 (used_roles 무시)
        char_props = []
        para_props = []
        styles = []
        
        for role, style_def in ROLE_STYLES.items():
            char_id = self.next_char_id
            para_id = self.next_para_id
            style_id = self.next_style_id
            
            self.role_to_style_id[role] = style_id
            self.role_to_para_id[role] = para_id    # 🆕
            self.role_to_char_id[role] = char_id    # 🆕
            
            # charPr 생성
            char_props.append(self._make_char_pr(char_id, style_def))
            
            # paraPr 생성
            para_props.append(self._make_para_pr(para_id, style_def))
            
            # style 생성
            styles.append(self._make_style(style_id, style_def.name, para_id, char_id))
            
            self.next_char_id += 1
            self.next_para_id += 1
            self.next_style_id += 1
        
        if not styles:
            print("   [정보] 주입할 스타일 없음")
            return
        
        # charProperties에 추가
        content = self._insert_before_tag(
            content, '</hh:charProperties>', '\n'.join(char_props) + '\n'
        )
        
        # paraProperties에 추가
        content = self._insert_before_tag(
            content, '</hh:paraProperties>', '\n'.join(para_props) + '\n'
        )
        
        # styles에 추가
        content = self._insert_before_tag(
            content, '</hh:styles>', '\n'.join(styles) + '\n'
        )
        
        # 🆕 numbering id=1 패턴 교체 (idRef="0"이 참조하는 기본 번호 모양)
        # 이렇게 하면 개요 자동 번호가 "제1장, 1.1, 1.1.1..." 형식으로 동작!
        content = self._replace_default_numbering(content)
        
        # itemCnt 업데이트
        content = self._update_item_counts(content)
        
        header_path.write_text(content, encoding='utf-8')
        print(f"   → header.xml 수정 완료 ({len(styles)}개 스타일 추가)")
    
    def _make_char_pr(self, id: int, style: StyleDefinition) -> str:
        """charPr XML 생성 (한 줄로!)"""
        color = style.font_color.lstrip('#')
        font_id = "1" if style.font_bold else "0"
        
        return f'<hh:charPr id="{id}" height="{style.font_size}" textColor="#{color}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"><hh:fontRef hangul="{font_id}" latin="{font_id}" hanja="{font_id}" japanese="{font_id}" other="{font_id}" symbol="{font_id}" user="{font_id}"/><hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/><hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/><hh:underline type="NONE" shape="SOLID" color="#000000"/><hh:strikeout shape="NONE" color="#000000"/><hh:outline type="NONE"/><hh:shadow type="NONE" color="#B2B2B2" offsetX="10" offsetY="10"/></hh:charPr>'
    
    def _make_para_pr(self, id: int, style: StyleDefinition) -> str:
        """paraPr XML 생성 (한 줄로!)"""
        # 개요 문단이면 type="OUTLINE", 아니면 type="NONE"
        # idRef="0"은 numbering id=1 (기본 번호 모양)을 참조
        if style.outline_level >= 0:
            heading = f'<hh:heading type="OUTLINE" idRef="0" level="{style.outline_level}"/>'
        else:
            heading = '<hh:heading type="NONE" idRef="0" level="0"/>'
        
        return f'<hh:paraPr id="{id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="0" suppressLineNumbers="0" checked="0"><hh:align horizontal="{style.align}" vertical="BASELINE"/>{heading}<hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="KEEP_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/><hh:autoSpacing eAsianEng="0" eAsianNum="0"/><hh:margin><hc:intent value="{style.indent_first}" unit="HWPUNIT"/><hc:left value="{style.indent_left}" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="{style.space_before}" unit="HWPUNIT"/><hc:next value="{style.space_after}" unit="HWPUNIT"/></hh:margin><hh:lineSpacing type="PERCENT" value="{style.line_spacing}" unit="HWPUNIT"/><hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/></hh:paraPr>'
    
    def _make_style(self, id: int, name: str, para_id: int, char_id: int) -> str:
        """style XML 생성"""
        safe_name = name.replace('<', '&lt;').replace('>', '&gt;')
        return f'<hh:style id="{id}" type="PARA" name="{safe_name}" engName="" paraPrIDRef="{para_id}" charPrIDRef="{char_id}" nextStyleIDRef="{id}" langID="1042" lockForm="0"/>'
    
    def _insert_before_tag(self, content: str, tag: str, insert_text: str) -> str:
        """특정 태그 앞에 텍스트 삽입"""
        return content.replace(tag, insert_text + tag)
    
    def _update_item_counts(self, content: str) -> str:
        """itemCnt 속성 업데이트"""
        # charProperties itemCnt
        char_count = content.count('<hh:charPr ')
        content = re.sub(
            r'<hh:charProperties itemCnt="(\d+)"',
            f'<hh:charProperties itemCnt="{char_count}"',
            content
        )
        
        # paraProperties itemCnt
        para_count = content.count('<hh:paraPr ')
        content = re.sub(
            r'<hh:paraProperties itemCnt="(\d+)"',
            f'<hh:paraProperties itemCnt="{para_count}"',
            content
        )
        
        # styles itemCnt
        style_count = content.count('<hh:style ')
        content = re.sub(
            r'<hh:styles itemCnt="(\d+)"',
            f'<hh:styles itemCnt="{style_count}"',
            content
        )
        
        # 🆕 numberings itemCnt
        numbering_count = content.count('<hh:numbering ')
        content = re.sub(
            r'<hh:numberings itemCnt="(\d+)"',
            f'<hh:numberings itemCnt="{numbering_count}"',
            content
        )
        
        return content
    
    def _replace_default_numbering(self, content: str) -> str:
        """numbering id=1의 패턴을 우리 패턴으로 교체"""
        # 우리가 원하는 개요 번호 패턴
        new_patterns = [
            {'level': '1', 'format': 'DIGIT', 'pattern': '제^1장'},
            {'level': '2', 'format': 'DIGIT', 'pattern': '^1.^2'},
            {'level': '3', 'format': 'DIGIT', 'pattern': '^1.^2.^3'},
            {'level': '4', 'format': 'HANGUL_SYLLABLE', 'pattern': '^4.'},
            {'level': '5', 'format': 'DIGIT', 'pattern': '^5)'},
            {'level': '6', 'format': 'HANGUL_SYLLABLE', 'pattern': '^6)'},
            {'level': '7', 'format': 'CIRCLED_DIGIT', 'pattern': '^7'},
        ]
        
        # numbering id="1" 찾기
        match = re.search(r'(<hh:numbering id="1"[^>]*>)(.*?)(</hh:numbering>)', content, re.DOTALL)
        if not match:
            print("   [경고] numbering id=1 없음, 교체 건너뜀")
            return content
        
        numbering_content = match.group(2)
        
        for np in new_patterns:
            level = np['level']
            fmt = np['format']
            pattern = np['pattern']
            
            # 해당 level의 paraHead 찾아서 교체
            def replace_parahead(m):
                tag = m.group(0)
                # numFormat 변경
                tag = re.sub(r'numFormat="[^"]*"', f'numFormat="{fmt}"', tag)
                # 패턴(텍스트 내용) 변경
                tag = re.sub(r'>([^<]*)</hh:paraHead>', f'>{pattern}</hh:paraHead>', tag)
                return tag
            
            numbering_content = re.sub(
                rf'<hh:paraHead[^>]*level="{level}"[^>]*>.*?</hh:paraHead>',
                replace_parahead,
                numbering_content
            )
        
        new_content = match.group(1) + numbering_content + match.group(3)
        print("   [INFO] numbering id=1 패턴 교체 완료 (제^1장, ^1.^2, ^1.^2.^3...)")
        return content.replace(match.group(0), new_content)

    def _adjust_tables(self, content: str) -> str:
        """표 셀 크기 자동 조정
        
        1. 행 높이: 최소 800 hwpunit (내용 잘림 방지)
        2. 열 너비: 표 전체 너비를 열 개수로 균등 분배 (또는 첫 열 좁게)
        """
        
        def adjust_table(match):
            tbl = match.group(0)
            
            # 표 전체 너비 추출
            sz_match = re.search(r'<hp:sz width="(\d+)"', tbl)
            table_width = int(sz_match.group(1)) if sz_match else 47624
            
            # 열 개수 추출
            col_match = re.search(r'colCnt="(\d+)"', tbl)
            col_cnt = int(col_match.group(1)) if col_match else 4
            
            # 열 너비 계산 (첫 열은 30%, 나머지 균등)
            first_col_width = int(table_width * 0.25)
            other_col_width = (table_width - first_col_width) // (col_cnt - 1) if col_cnt > 1 else table_width
            
            # 행 높이 최소값 설정
            min_height = 800  # 약 8mm
            
            # 셀 크기 조정
            col_idx = [0]  # closure용
            
            def adjust_cell_sz(cell_match):
                width = int(cell_match.group(1))
                height = int(cell_match.group(2))
                
                # 높이 조정
                new_height = max(height, min_height)
                
                return f'<hp:cellSz width="{width}" height="{new_height}"/>'
            
            tbl = re.sub(
                r'<hp:cellSz width="(\d+)" height="(\d+)"/>',
                adjust_cell_sz,
                tbl
            )
            
            return tbl
        
        return re.sub(r'<hp:tbl[^>]*>.*?</hp:tbl>', adjust_table, content, flags=re.DOTALL)

    def _inject_section_styles(self, role_positions: Dict[str, List[tuple]]):
        """section*.xml에 styleIDRef 매핑 (텍스트 매칭 방식)"""
        contents_dir = self.temp_dir / "Contents"
        
        # 🔍 디버그: role_to_style_id 확인
        print(f"   [DEBUG] role_to_style_id: {self.role_to_style_id}")
        
        # section 파일들 찾기
        section_files = sorted(contents_dir.glob("section*.xml"))
        print(f"   [DEBUG] section files: {[f.name for f in section_files]}")
        
        total_modified = 0
        
        for section_file in section_files:
            print(f"   [DEBUG] Processing: {section_file.name}")
            original_content = section_file.read_text(encoding='utf-8')
            print(f"   [DEBUG] File size: {len(original_content)} bytes")
            
            content = original_content  # 작업용 복사본
            
            # 🆕 머리말/꼬리말 영역 보존 (placeholder로 교체)
            header_footer_map = {}
            placeholder_idx = 0
            
            def save_header_footer(match):
                nonlocal placeholder_idx
                key = f"__HF_PLACEHOLDER_{placeholder_idx}__"
                header_footer_map[key] = match.group(0)
                placeholder_idx += 1
                return key
            
            # 머리말/꼬리말 임시 교체
            content = re.sub(r'<hp:header[^>]*>.*?</hp:header>', save_header_footer, content, flags=re.DOTALL)
            content = re.sub(r'<hp:footer[^>]*>.*?</hp:footer>', save_header_footer, content, flags=re.DOTALL)
            
            # 모든 <hp:p> 태그와 내부 텍스트 추출
            para_pattern = r'(<hp:p [^>]*>)(.*?)(</hp:p>)'
            
            section_modified = 0
            
            def replace_style(match):
                nonlocal total_modified, section_modified
                open_tag = match.group(1)
                inner = match.group(2)
                close_tag = match.group(3)
                
                # 텍스트 추출 (태그 제거)
                text = re.sub(r'<[^>]+>', '', inner).strip()
                if not text:
                    return match.group(0)
                
                # 텍스트 앞부분으로 역할 판단
                text_start = text[:50]  # 처음 50자로 판단
                
                matched_role = None
                matched_style_id = None
                matched_para_id = None
                matched_char_id = None
                
                # 제목 패턴 매칭 (앞에 특수문자 허용)
                # Unicode: ■\u25a0 ▸\u25b8 ◆\u25c6 ▶\u25b6 ●\u25cf ○\u25cb ▪\u25aa ►\u25ba ☞\u261e ★\u2605 ※\u203b ·\u00b7
                prefix = r'^[\u25a0\u25b8\u25c6\u25b6\u25cf\u25cb\u25aa\u25ba\u261e\u2605\u203b\u00b7\s]*'
                
                # 🆕 FIGURE_CAPTION: "[그림 1-1]", "[그림 1-2]" 등 (가장 먼저 체크!)
                # 그림 = \uadf8\ub9bc
                if re.match(r'^\[\uadf8\ub9bc\s*[\d-]+\]', text_start):
                    matched_role = 'FIGURE_CAPTION'
                # 🆕 TABLE_CAPTION: "<표 1-1>", "[표 1-1]" 등
                # 표 = \ud45c
                elif re.match(r'^[<\[]\ud45c\s*[\d-]+[>\]]', text_start):
                    matched_role = 'TABLE_CAPTION'
                # H1: "제1장", "1 개요" 등
                elif re.match(prefix + r'\uc81c?\s*\d+\uc7a5?\s', text_start) or re.match(prefix + r'[1-9]\s+[\uac00-\ud7a3]', text_start):
                    matched_role = 'H1'
                # H3: "1.1.1 " (H2보다 먼저 체크!)
                elif re.match(prefix + r'\d+\.\d+\.\d+\s', text_start):
                    matched_role = 'H3'
                # H2: "1.1 "
                elif re.match(prefix + r'\d+\.\d+\s', text_start):
                    matched_role = 'H2'
                # H4: "가. "
                elif re.match(prefix + r'[\uac00-\ud7a3]\.\s', text_start):
                    matched_role = 'H4'
                # H5: "1) "
                elif re.match(prefix + r'\d+\)\s', text_start):
                    matched_role = 'H5'
                # H6: "(1) " 또는 "가) "
                elif re.match(prefix + r'\(\d+\)\s', text_start):
                    matched_role = 'H6'
                elif re.match(prefix + r'[\uac00-\ud7a3]\)\s', text_start):
                    matched_role = 'H6'
                # LIST_ITEM: "○ ", "● ", "• " 등
                elif re.match(r'^[\u25cb\u25cf\u25e6\u2022\u2023\u25b8]\s', text_start):
                    matched_role = 'LIST_ITEM'
                elif re.match(r'^[-\u2013\u2014]\s', text_start):
                    matched_role = 'LIST_ITEM'
                
                # 매칭된 역할이 있고 스타일 ID가 있으면 적용
                if matched_role and matched_role in self.role_to_style_id:
                    matched_style_id = self.role_to_style_id[matched_role]
                    matched_para_id = self.role_to_para_id[matched_role]
                    matched_char_id = self.role_to_char_id[matched_role]
                elif 'BODY' in self.role_to_style_id and len(text) > 20:
                    # 긴 텍스트는 본문으로 간주
                    matched_role = 'BODY'
                    matched_style_id = self.role_to_style_id['BODY']
                    matched_para_id = self.role_to_para_id['BODY']
                    matched_char_id = self.role_to_char_id['BODY']
                
                if matched_style_id:
                    # 1. hp:p 태그의 styleIDRef 변경
                    if 'styleIDRef="' in open_tag:
                        new_open = re.sub(r'styleIDRef="[^"]*"', f'styleIDRef="{matched_style_id}"', open_tag)
                    else:
                        new_open = open_tag.replace('<hp:p ', f'<hp:p styleIDRef="{matched_style_id}" ')
                    
                    # 2. hp:p 태그의 paraPrIDRef도 변경! (스타일의 paraPrIDRef와 일치!)
                    new_open = re.sub(r'paraPrIDRef="[^"]*"', f'paraPrIDRef="{matched_para_id}"', new_open)
                    
                    # 3. inner에서 hp:run의 charPrIDRef도 변경! (스타일의 charPrIDRef와 일치!)
                    new_inner = re.sub(r'(<hp:run[^>]*charPrIDRef=")[^"]*(")', f'\\g<1>{matched_char_id}\\2', inner)
                    
                    # 🆕 4. 개요 문단이면 수동 번호 제거 (자동 번호가 붙으니까!)
                    if matched_role in ROLE_STYLES and ROLE_STYLES[matched_role].outline_level >= 0:
                        new_inner = self._remove_manual_numbering(new_inner, matched_role)
                    
                    total_modified += 1
                    section_modified += 1
                    return new_open + new_inner + close_tag
                
                return match.group(0)
            
            new_content = re.sub(para_pattern, replace_style, content, flags=re.DOTALL)
            
            # 🆕 표 크기 자동 조정
            new_content = self._adjust_tables(new_content)
            
            # 🆕 outlineShapeIDRef를 1로 변경 (우리가 교체한 numbering id=1 사용)
            new_content = re.sub(
                r'outlineShapeIDRef="[^"]*"',
                'outlineShapeIDRef="1"',
                new_content
            )

 
            # 🆕 머리말/꼬리말 복원
            for key, original in header_footer_map.items():
                new_content = new_content.replace(key, original)
            
            print(f"   [DEBUG] {section_file.name}: {section_modified} paras modified, content changed: {new_content != original_content}")
            
            if new_content != original_content:
                section_file.write_text(new_content, encoding='utf-8')
                print(f"   -> {section_file.name} saved")
        
        print(f"   -> Total {total_modified} paragraphs styled")
    
    def _update_para_style(self, content: str, para_idx: int, style_id: int) -> str:
        """특정 인덱스의 문단 styleIDRef 변경"""
        # <hp:p ...> 태그들 찾기
        pattern = r'<hp:p\s[^>]*>'
        matches = list(re.finditer(pattern, content))
        
        if para_idx >= len(matches):
            return content
        
        match = matches[para_idx]
        old_tag = match.group(0)
        
        # styleIDRef 속성 변경 또는 추가
        if 'styleIDRef=' in old_tag:
            new_tag = re.sub(r'styleIDRef="[^"]*"', f'styleIDRef="{style_id}"', old_tag)
        else:
            # 속성 추가
            new_tag = old_tag.replace('<hp:p ', f'<hp:p styleIDRef="{style_id}" ')
        
        return content[:match.start()] + new_tag + content[match.end():]
    
    def _remove_manual_numbering(self, inner: str, role: str) -> str:
        """🆕 개요 문단에서 수동 번호 제거 (자동 번호가 붙으니까!)
        
        HTML에서 "제1장 DX 개요" → "DX 개요" (자동으로 "제1장" 붙음)
        HTML에서 "1.1 측량 DX" → "측량 DX" (자동으로 "1.1" 붙음)
        """
        # 역할별 번호 패턴
        patterns = {
            'H1': r'^(제\s*\d+\s*장\s*)',          # "제1장 " → 제거
            'H2': r'^(\d+\.\d+\s+)',               # "1.1 " → 제거
            'H3': r'^(\d+\.\d+\.\d+\s+)',          # "1.1.1 " → 제거
            'H4': r'^([가-힣]\.\s+)',              # "가. " → 제거
            'H5': r'^(\d+\)\s+)',                  # "1) " → 제거
            'H6': r'^([가-힣]\)\s+|\(\d+\)\s+)',   # "가) " 또는 "(1) " → 제거
            'H7': r'^([①②③④⑤⑥⑦⑧⑨⑩]+\s*)',   # "① " → 제거
        }
        
        if role not in patterns:
            return inner
        
        pattern = patterns[role]
        
        # <hp:t> 태그 내 텍스트에서 번호 제거
        def remove_number(match):
            text = match.group(1)
            # 첫 번째 <hp:t> 내용에서만 번호 제거
            new_text = re.sub(pattern, '', text, count=1)
            return f'<hp:t>{new_text}</hp:t>'
        
        # 첫 번째 hp:t 태그만 처리
        new_inner = re.sub(r'<hp:t>([^<]*)</hp:t>', remove_number, inner, count=1)
        
        return new_inner
    
    def _repack_hwpx(self, output_path: str):
        """HWPX 재압축"""
        print(f"   [DEBUG] Repacking to: {output_path}")
        print(f"   [DEBUG] Source dir: {self.temp_dir}")
        
        # 압축 전 section 파일 크기 확인
        for sec in ['section0.xml', 'section1.xml', 'section2.xml']:
            sec_path = self.temp_dir / "Contents" / sec
            if sec_path.exists():
                print(f"   [DEBUG] {sec} size before zip: {sec_path.stat().st_size} bytes")
        
        # 🆕 임시 파일에 먼저 저장 (원본 파일 잠금 문제 회피)
        temp_output = output_path + ".tmp"
        
        with zipfile.ZipFile(temp_output, 'w', zipfile.ZIP_DEFLATED) as zf:
            # mimetype은 압축 없이 첫 번째로
            mimetype_path = self.temp_dir / "mimetype"
            if mimetype_path.exists():
                zf.write(mimetype_path, "mimetype", compress_type=zipfile.ZIP_STORED)
            
            # 나머지 파일들
            file_count = 0
            for root, dirs, files in os.walk(self.temp_dir):
                for file in files:
                    if file == "mimetype":
                        continue
                    file_path = Path(root) / file
                    arcname = file_path.relative_to(self.temp_dir)
                    zf.write(file_path, arcname)
                    file_count += 1
            
            print(f"   [DEBUG] Total files zipped: {file_count}")
        
        # 🆕 원본 삭제 후 임시 파일을 원본 이름으로 변경
        import time
        for attempt in range(3):
            try:
                if os.path.exists(output_path):
                    os.remove(output_path)
                os.rename(temp_output, output_path)
                break
            except PermissionError:
                print(f"   [DEBUG] 파일 잠금 대기 중... ({attempt + 1}/3)")
                time.sleep(0.5)
        else:
            # 3번 시도 실패 시 임시 파일 이름으로 유지
            print(f"   [경고] 원본 덮어쓰기 실패, 임시 파일 사용: {temp_output}")
            output_path = temp_output
        
        # 압축 후 결과 확인
        print(f"   [DEBUG] Output file size: {Path(output_path).stat().st_size} bytes")


def inject_styles_to_hwpx(hwpx_path: str, elements: list) -> str:
    """
    편의 함수: StyledElement 리스트로부터 역할 위치 추출 후 스타일 주입
    
    Args:
        hwpx_path: HWPX 파일 경로
        elements: StyleAnalyzer의 StyledElement 리스트
    
    Returns:
        수정된 HWPX 파일 경로
    """
    # 역할별 위치 수집
    # 참고: 현재는 section 0, para 순서대로 가정
    role_positions: Dict[str, List[tuple]] = {}
    
    for idx, elem in enumerate(elements):
        role = elem.role
        if role not in role_positions:
            role_positions[role] = []
        # (section_idx, para_idx) - 현재는 section 0 가정
        role_positions[role].append((0, idx))
    
    injector = HwpxStyleInjector()
    return injector.inject(hwpx_path, role_positions)


# 테스트
if __name__ == "__main__":
    # 테스트용
    test_positions = {
        'H1': [(0, 0), (0, 5)],
        'H2': [(0, 1), (0, 6)],
        'BODY': [(0, 2), (0, 3), (0, 4)],
    }
    
    # injector = HwpxStyleInjector()
    # injector.inject("test.hwpx", test_positions)
    print("HwpxStyleInjector 모듈 로드 완료")