# -*- coding: utf-8 -*-
"""
HTML → HWP 변환기 v11

✅ 이미지: sizeoption=0 (원본 크기) 또는 width/height 지정
✅ 페이지번호: ctrl 코드 방식으로 수정
✅ 나머지는 v10 유지

pip install pyhwpx beautifulsoup4 pillow
"""

from pyhwpx import Hwp
from bs4 import BeautifulSoup, NavigableString
import os, re

# 스타일 그루핑 시스템 추가
from converters.style_analyzer import StyleAnalyzer, StyledElement
from converters.hwp_style_mapping import HwpStyleMapper, DEFAULT_STYLES, ROLE_TO_STYLE_NAME
from converters.hwpx_style_injector import HwpxStyleInjector, inject_styles_to_hwpx


# PIL 선택적 import (이미지 크기 확인용)
try:
    from PIL import Image
    HAS_PIL = True
except ImportError:
    HAS_PIL = False
    print("[알림] PIL 없음 - 이미지 원본 크기로 삽입")

class Config:
    MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM = 20, 20, 20, 15
    HEADER_LEN, FOOTER_LEN = 10, 10
    MAX_IMAGE_WIDTH = 150  # mm (최대 이미지 너비)
    ASSETS_PATH = r"D:\for python\geulbeot-light\geulbeot-light\output\assets"  # 🆕 추가

class StyleParser:
    def __init__(self):
        self.style_map = {}  # 스타일 매핑 (역할 → HwpStyle)
        self.sty_gen = None  # 스타일 생성기
        self.class_styles = {
            'h1': {'font-size': '20pt', 'color': '#008000'},
            'h2': {'font-size': '16pt', 'color': '#03581d'},
            'h3': {'font-size': '13pt', 'color': '#228B22'},
            'p': {'font-size': '11pt', 'color': '#333333'},
            'li': {'font-size': '11pt', 'color': '#333333'},
            'th': {'font-size': '9pt', 'color': '#006400'},
            'td': {'font-size': '9.5pt', 'color': '#333333'},
            'toc-lvl-1': {'font-size': '13pt', 'font-weight': '900', 'color': '#006400'},
            'toc-lvl-2': {'font-size': '11pt', 'color': '#333333'},
            'toc-lvl-3': {'font-size': '10pt', 'color': '#666666'},
        }
    
    def get_element_style(self, elem):
        style = {}
        tag = elem.name if hasattr(elem, 'name') else None
        if tag and tag in self.class_styles: style.update(self.class_styles[tag])
        for cls in elem.get('class', []) if hasattr(elem, 'get') else []:
            if cls in self.class_styles: style.update(self.class_styles[cls])
        return style
    
    def parse_size(self, s):
        m = re.search(r'([\d.]+)', str(s)) if s else None
        return float(m.group(1)) if m else 11
    
    def parse_color(self, c):
        if not c: return '#000000'
        c = str(c).strip().lower()
        if re.match(r'^#[0-9a-fA-F]{6}$', c): return c.upper()
        m = re.search(r'rgb[a]?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)', c)
        return f'#{int(m.group(1)):02X}{int(m.group(2)):02X}{int(m.group(3)):02X}' if m else '#000000'
    
    def is_bold(self, style): return style.get('font-weight', '') in ['bold', '700', '800', '900']

# ═══════════════════════════════════════════════════════════════
# 번호 제거 유틸리티
# ═══════════════════════════════════════════════════════════════

NUMBERING_PATTERNS = {
    'H1': re.compile(r'^(\d+)\.\s*'),           # "1. " → ""
    'H2': re.compile(r'^(\d+)\.(\d+)\s*'),      # "1.1 " → ""
    'H3': re.compile(r'^(\d+)\.(\d+)\.(\d+)\s*'), # "1.1.1 " → ""
    'H4': re.compile(r'^[가-하]\.\s*'),          # "가. " → ""
    'H5': re.compile(r'^(\d+)\)\s*'),           # "1) " → ""
    'H6': re.compile(r'^\((\d+)\)\s*'),         # "(1) " → ""
    'H7': re.compile(r'^[①②③④⑤⑥⑦⑧⑨⑩]\s*'),  # "① " → ""
    'LIST_ITEM': re.compile(r'^[•\-○]\s*'),    # "• " → ""
}

def strip_numbering(text: str, role: str) -> str:
    """
    역할에 따라 텍스트 앞의 번호/기호 제거
    HWP 개요 기능이 번호를 자동 생성하므로 중복 방지
    """
    if not text:
        return text
    
    pattern = NUMBERING_PATTERNS.get(role)
    if pattern:
        return pattern.sub('', text).strip()
    
    return text.strip()

# ═══════════════════════════════════════════════════════════════
# 표 너비 파싱 유틸리티 (🆕 추가)
# ═══════════════════════════════════════════════════════════════

def _parse_width(width_str):
    """너비 문자열 파싱 → mm 값 반환"""
    if not width_str:
        return None
    
    width_str = str(width_str).strip().lower()
    
    # style 속성에서 width 추출
    style_match = re.search(r'width\s*:\s*([^;]+)', width_str)
    if style_match:
        width_str = style_match.group(1).strip()
    
    # px → mm (96 DPI 기준)
    px_match = re.search(r'([\d.]+)\s*px', width_str)
    if px_match:
        return float(px_match.group(1)) * 25.4 / 96
    
    # mm 그대로
    mm_match = re.search(r'([\d.]+)\s*mm', width_str)
    if mm_match:
        return float(mm_match.group(1))
    
    # % → 본문폭(170mm) 기준 계산
    pct_match = re.search(r'([\d.]+)\s*%', width_str)
    if pct_match:
        return float(pct_match.group(1)) * 170 / 100
    
    # 숫자만 있으면 px로 간주
    num_match = re.search(r'^([\d.]+)$', width_str)
    if num_match:
        return float(num_match.group(1)) * 25.4 / 96
    
    return None


def _parse_align(cell):
    """셀의 정렬 속성 파싱"""
    align = cell.get('align', '').lower()
    if align in ['left', 'center', 'right']:
        return align
    
    style = cell.get('style', '')
    align_match = re.search(r'text-align\s*:\s*(\w+)', style)
    if align_match:
        return align_match.group(1).lower()
    
    return None


def _parse_bg_color(cell):
    """셀의 배경색 파싱"""
    bgcolor = cell.get('bgcolor', '')
    if bgcolor:
        return bgcolor if bgcolor.startswith('#') else f'#{bgcolor}'
    
    style = cell.get('style', '')
    bg_match = re.search(r'background(?:-color)?\s*:\s*([^;]+)', style)
    if bg_match:
        color = bg_match.group(1).strip()
        if color.startswith('#'):
            return color
        rgb_match = re.search(r'rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)', color)
        if rgb_match:
            r, g, b = int(rgb_match.group(1)), int(rgb_match.group(2)), int(rgb_match.group(3))
            return f'#{r:02X}{g:02X}{b:02X}'
    
    return None


class HtmlToHwpConverter:
    def __init__(self, visible=True):
        self.hwp = Hwp(visible=visible)
        self.cfg = Config()
        self.sp = StyleParser()
        self.base_path = ""
        self.is_first_h1 = True
        self.image_count = 0
        self.table_widths = []  # 🆕 표 열 너비 정보 저장용
        self.style_map = {}  # 역할 → 스타일 이름 매핑
        self.sty_path = None  # .sty 파일 경로
    
    def _mm(self, mm): return self.hwp.MiliToHwpUnit(mm)
    def _pt(self, pt): return self.hwp.PointToHwpUnit(pt)
    def _rgb(self, c):
        c = c.lstrip('#')
        return self.hwp.RGBColor(int(c[0:2],16), int(c[2:4],16), int(c[4:6],16)) if len(c)>=6 else self.hwp.RGBColor(0,0,0)
    
    def _setup_page(self):
        try:
            self.hwp.HAction.GetDefault("PageSetup", self.hwp.HParameterSet.HSecDef.HSet)
            s = self.hwp.HParameterSet.HSecDef
            s.PageDef.LeftMargin = self._mm(self.cfg.MARGIN_LEFT)
            s.PageDef.RightMargin = self._mm(self.cfg.MARGIN_RIGHT)
            s.PageDef.TopMargin = self._mm(self.cfg.MARGIN_TOP)
            s.PageDef.BottomMargin = self._mm(self.cfg.MARGIN_BOTTOM)
            s.PageDef.HeaderLen = self._mm(self.cfg.HEADER_LEN)
            s.PageDef.FooterLen = self._mm(self.cfg.FOOTER_LEN)
            self.hwp.HAction.Execute("PageSetup", s.HSet)
        except: pass
    
    def _create_header(self, right_text=""):
        print(f"  → 머리말 생성: {right_text if right_text else '(초기화)'}")
        try:
            self.hwp.HAction.GetDefault("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
            self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterStyle", 0)
            self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterCtrlType", 0)
            self.hwp.HAction.Execute("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
            
            self.hwp.HAction.Run("ParagraphShapeAlignRight")
            self._set_font(9, False, '#333333')
            if right_text:
                self.hwp.insert_text(right_text)
            
            self.hwp.HAction.Run("CloseEx")
        except Exception as e:
            print(f"    [경고] 머리말: {e}")
    
    # ═══════════════════════════════════════════════════════════════
    # 꼬리말 - 페이지 번호 (수정)
    # ═══════════════════════════════════════════════════════════════
    def _create_footer(self, left_text=""):
        print(f"  → 꼬리말: {left_text}")
        
        # 1. 꼬리말 열기
        self.hwp.HAction.GetDefault("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
        self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterStyle", 0)
        self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterCtrlType", 1)
        self.hwp.HAction.Execute("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
        
        # 2. 좌측 정렬 + 제목 8pt
        self.hwp.HAction.Run("ParagraphShapeAlignLeft")
        self._set_font(8, False, '#666666')
        self.hwp.insert_text(left_text)
        
        # 3. 꼬리말 닫기
        self.hwp.HAction.Run("CloseEx")
        
        # 4. 쪽번호 (우측 하단)
        self.hwp.HAction.GetDefault("PageNumPos", self.hwp.HParameterSet.HPageNumPos.HSet)
        self.hwp.HParameterSet.HPageNumPos.DrawPos = self.hwp.PageNumPosition("BottomRight")
        self.hwp.HAction.Execute("PageNumPos", self.hwp.HParameterSet.HPageNumPos.HSet)

    def _new_section_with_header(self, header_text):
        """새 구역 생성 후 머리말 설정"""
        print(f"    → 새 구역 머리말: {header_text}")
        try:
            self.hwp.HAction.Run("BreakSection")
            
            self.hwp.HAction.GetDefault("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
            self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterStyle", 0)
            self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterCtrlType", 0)
            self.hwp.HAction.Execute("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
            
            self.hwp.HAction.Run("SelectAll")
            self.hwp.HAction.Run("Delete")
            
            self.hwp.HAction.Run("ParagraphShapeAlignRight")
            self._set_font(9, False, '#333333')
            self.hwp.insert_text(header_text)
            
            self.hwp.HAction.Run("CloseEx")
        except Exception as e:
            print(f"    [경고] 구역 머리말: {e}")

    # 스타일 적용 관련 (🆕 NEW)

    def _load_style_template(self, sty_path: str):
        """
        .sty 스타일 템플릿 로드
        HWP에서 스타일 불러오기 기능 사용
        """
        if not os.path.exists(sty_path):
            print(f"  [경고] 스타일 파일 없음: {sty_path}")
            return False
        
        try:
            # HWP 스타일 불러오기
            self.hwp.HAction.GetDefault("StyleTemplate", self.hwp.HParameterSet.HStyleTemplate.HSet)
            self.hwp.HParameterSet.HStyleTemplate.filename = sty_path
            self.hwp.HAction.Execute("StyleTemplate", self.hwp.HParameterSet.HStyleTemplate.HSet)
            print(f"  ✅ 스타일 템플릿 로드: {sty_path}")
            return True
        except Exception as e:
            print(f"  [경고] 스타일 로드 실패: {e}")
            return False


    def _apply_style_by_name(self, style_name: str):
        """
        현재 문단에 스타일 이름으로 적용
        텍스트 삽입 후 호출
        """
        try:
            # 현재 문단 선택
            self.hwp.HAction.Run("MoveLineBegin")
            self.hwp.HAction.Run("MoveSelLineEnd")
            
            # 스타일 적용
            self.hwp.HAction.GetDefault("Style", self.hwp.HParameterSet.HStyle.HSet)
            self.hwp.HParameterSet.HStyle.StyleName = style_name
            self.hwp.HAction.Execute("Style", self.hwp.HParameterSet.HStyle.HSet)
            
            # 커서 문단 끝으로
            self.hwp.HAction.Run("MoveLineEnd")
            
        except Exception as e:
            print(f"  [경고] 스타일 적용 실패 '{style_name}': {e}")


    def _build_dynamic_style_map(self, elements: list):
        """HTML 분석 결과 기반 동적 스타일 매핑 생성 (숫자)"""
        roles = set(elem.role for elem in elements)
        
        # 제목 역할 정렬 (H1, H2, H3...)
        title_roles = sorted([r for r in roles if r.startswith('H') and r[1:].isdigit()],
                            key=lambda x: int(x[1:]))
        
        # 기타 역할
        other_roles = [r for r in roles if r not in title_roles]
        
        # 순차 할당 (개요 1~10)
        self.style_map = {}
        style_num = 1
        
        for role in title_roles:
            if style_num <= 10:
                self.style_map[role] = style_num
                style_num += 1
        
        for role in other_roles:
            if style_num <= 10:
                self.style_map[role] = style_num
                style_num += 1
        
        print(f"  📝 동적 스타일 매핑: {self.style_map}")
        return self.style_map



    def _set_font(self, size=11, bold=False, color='#000000'):
        self.hwp.set_font(FaceName='맑은 고딕', Height=size, Bold=bold, TextColor=self._rgb(color))
    
    def _set_para(self, align='justify', lh=170, left=0, indent=0, before=0, after=0):
        acts = {'left':'ParagraphShapeAlignLeft','center':'ParagraphShapeAlignCenter',
                'right':'ParagraphShapeAlignRight','justify':'ParagraphShapeAlignJustify'}
        if align in acts: self.hwp.HAction.Run(acts[align])
        try:
            self.hwp.HAction.GetDefault("ParagraphShape", self.hwp.HParameterSet.HParaShape.HSet)
            p = self.hwp.HParameterSet.HParaShape
            p.LineSpaceType, p.LineSpacing = 0, lh
            p.LeftMargin = self._mm(left)
            p.IndentMargin = self._mm(indent)
            p.SpaceBeforePara = self._pt(before)
            p.SpaceAfterPara = self._pt(after)
            p.BreakNonLatinWord = 0
            self.hwp.HAction.Execute("ParagraphShape", p.HSet)
        except: pass
    
    def _set_cell_bg(self, color):
        try:
            self.hwp.HAction.GetDefault("CellBorderFill", self.hwp.HParameterSet.HCellBorderFill.HSet)
            p = self.hwp.HParameterSet.HCellBorderFill
            p.FillAttr.type = self.hwp.BrushType("NullBrush|WinBrush")
            p.FillAttr.WinBrushFaceStyle = self.hwp.HatchStyle("None")
            p.FillAttr.WinBrushHatchColor = self._rgb('#000000')
            p.FillAttr.WinBrushFaceColor = self._rgb(color)
            p.FillAttr.WindowsBrush = 1
            self.hwp.HAction.Execute("CellBorderFill", p.HSet)
        except: pass
    
    def _underline_box(self, text, size=14, color='#008000'):
        try:
            self.hwp.HAction.GetDefault("TableCreate", self.hwp.HParameterSet.HTableCreation.HSet)
            t = self.hwp.HParameterSet.HTableCreation
            t.Rows, t.Cols, t.WidthType, t.HeightType = 1, 1, 0, 0
            t.WidthValue, t.HeightValue = self._mm(168), self._mm(10)
            self.hwp.HAction.Execute("TableCreate", t.HSet)
            self.hwp.HAction.GetDefault("InsertText", self.hwp.HParameterSet.HInsertText.HSet)
            self.hwp.HParameterSet.HInsertText.Text = text
            self.hwp.HAction.Execute("InsertText", self.hwp.HParameterSet.HInsertText.HSet)
            self.hwp.HAction.Run("TableCellBlock")
            self.hwp.HAction.GetDefault("CharShape", self.hwp.HParameterSet.HCharShape.HSet)
            self.hwp.HParameterSet.HCharShape.Height = self._pt(size)
            self.hwp.HParameterSet.HCharShape.TextColor = self._rgb(color)
            self.hwp.HAction.Execute("CharShape", self.hwp.HParameterSet.HCharShape.HSet)
            self.hwp.HAction.GetDefault("CellBorder", self.hwp.HParameterSet.HCellBorderFill.HSet)
            c = self.hwp.HParameterSet.HCellBorderFill
            c.BorderTypeTop = self.hwp.HwpLineType("None")
            c.BorderTypeRight = self.hwp.HwpLineType("None")
            c.BorderTypeLeft = self.hwp.HwpLineType("None")
            self.hwp.HAction.Execute("CellBorder", c.HSet)
            self.hwp.HAction.GetDefault("CellBorder", self.hwp.HParameterSet.HCellBorderFill.HSet)
            c = self.hwp.HParameterSet.HCellBorderFill
            c.BorderColorBottom = self._rgb(color)
            c.BorderWidthBottom = self.hwp.HwpLineWidth("0.4mm")
            self.hwp.HAction.Execute("CellBorder", c.HSet)
            self.hwp.HAction.Run("Cancel")
            self.hwp.HAction.Run("CloseEx")
            self.hwp.HAction.Run("MoveDocEnd")
        except:
            self._set_font(size, True, color)
            self.hwp.insert_text(text)
            self.hwp.BreakPara()
    
    def _update_header(self, new_title):
        """머리말 텍스트 업데이트"""
        try:
            # 기존 머리말 편집 모드로 진입
            self.hwp.HAction.GetDefault("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
            self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterStyle", 2)  # 편집 모드
            self.hwp.HParameterSet.HHeaderFooter.HSet.SetItem("HeaderFooterCtrlType", 0)
            self.hwp.HAction.Execute("HeaderFooter", self.hwp.HParameterSet.HHeaderFooter.HSet)
            
            # 기존 내용 삭제
            self.hwp.HAction.Run("SelectAll")
            self.hwp.HAction.Run("Delete")
            
            # 새 내용 삽입
            self.hwp.HAction.Run("ParagraphShapeAlignRight")
            self._set_font(9, False, '#333333')
            self.hwp.insert_text(new_title)
            
            self.hwp.HAction.Run("CloseEx")
        except Exception as e:
            print(f"    [경고] 머리말 업데이트: {e}")

    def _insert_heading(self, elem):
        lv = int(elem.name[1]) if elem.name in ['h1','h2','h3'] else 1
        txt = elem.get_text(strip=True)
        st = self.sp.get_element_style(elem)
        sz = self.sp.parse_size(st.get('font-size','14pt'))
        cl = self.sp.parse_color(st.get('color','#008000'))
        
        if lv == 1:
            if self.is_first_h1:
                self._create_header(txt)
                self.is_first_h1 = False
            else:
                self._new_section_with_header(txt)
            
            self._set_para('left', 130, before=0, after=0)
            self._underline_box(txt, sz, cl)
            self.hwp.BreakPara()
            self._set_para('left', 130, before=0, after=15)
            self.hwp.BreakPara()
        elif lv == 2:
            self._set_para('left', 150, before=20, after=8)
            self._set_font(sz, True, cl)
            self.hwp.insert_text("■ " + txt)
            self.hwp.BreakPara()
        elif lv == 3:
            self._set_para('left', 140, left=3, before=12, after=5)
            self._set_font(sz, True, cl)
            self.hwp.insert_text("▸ " + txt)
            self.hwp.BreakPara()
    
    def _insert_paragraph(self, elem):
        txt = elem.get_text(strip=True)
        if not txt: return
        st = self.sp.get_element_style(elem)
        sz = self.sp.parse_size(st.get('font-size','11pt'))
        cl = self.sp.parse_color(st.get('color','#333333'))
        self._set_para('justify', 170, left=0, indent=3, before=0, after=3)
        
        if elem.find(['b','strong']):
            for ch in elem.children:
                if isinstance(ch, NavigableString):
                    if str(ch).strip(): self._set_font(sz,False,cl); self.hwp.insert_text(str(ch))
                elif ch.name in ['b','strong']:
                    if ch.get_text(): self._set_font(sz,True,cl); self.hwp.insert_text(ch.get_text())
        else:
            self._set_font(sz, self.sp.is_bold(st), cl)
            self.hwp.insert_text(txt)
        self.hwp.BreakPara()
    
    def _insert_list(self, elem):
        lt = elem.name
        for i, li in enumerate(elem.find_all('li', recursive=False)):
            st = self.sp.get_element_style(li)
            cls = li.get('class', [])
            txt = li.get_text(strip=True)
            is_toc = any('toc-' in c for c in cls)
            
            if 'toc-lvl-1' in cls: left, bef = 0, 8
            elif 'toc-lvl-2' in cls: left, bef = 7, 3
            elif 'toc-lvl-3' in cls: left, bef = 14, 1
            else: left, bef = 4, 2
            
            pf = f"{i+1}. " if lt == 'ol' else "• "
            sz = self.sp.parse_size(st.get('font-size','11pt'))
            cl = self.sp.parse_color(st.get('color','#333333'))
            bd = self.sp.is_bold(st)
            
            if is_toc:
                self._set_para('left', 170, left=left, indent=0, before=bef, after=1)
                self._set_font(sz, bd, cl)
                self.hwp.insert_text(pf + txt)
                self.hwp.BreakPara()
            else:
                self._set_para('justify', 170, left=left, indent=0, before=bef, after=1)
                self._set_font(sz, bd, cl)
                self.hwp.insert_text(pf)
                self.hwp.HAction.Run("ParagraphShapeIndentAtCaret")
                self.hwp.insert_text(txt)
                self.hwp.BreakPara()
    
    def _insert_table(self, table_elem):
        """HTML 테이블 → HWP 표 변환 (내용 기반 열 너비 계산 + HWPX 후처리용 저장)"""
        
        # ═══ 1. 테이블 구조 분석 ═══
        rows_data = []
        cell_styles = {}
        occupied = {}
        max_cols = 0
        col_widths = []  # 열 너비 (mm) - HTML에서 지정된 값
        
        # <colgroup>/<col>에서 너비 추출
        colgroup = table_elem.find('colgroup')
        if colgroup:
            for col in colgroup.find_all('col'):
                width = _parse_width(col.get('width') or col.get('style', ''))
                col_widths.append(width)
        
        # 행 데이터 수집
        for ri, tr in enumerate(table_elem.find_all('tr')):
            row = []
            ci = 0
            
            for cell in tr.find_all(['td', 'th']):
                # 병합된 셀 건너뛰기
                while (ri, ci) in occupied:
                    row.append("")
                    ci += 1
                
                txt = cell.get_text(strip=True)
                cs = int(cell.get('colspan', 1))
                rs = int(cell.get('rowspan', 1))
                
                # 셀 스타일 저장
                cell_styles[(ri, ci)] = {
                    'is_header': cell.name == 'th' or ri == 0,
                    'align': _parse_align(cell),
                    'bg_color': _parse_bg_color(cell)
                }
                
                # 첫 행에서 열 너비 추출 (colgroup 없을 때)
                if ri == 0:
                    width = _parse_width(cell.get('width') or cell.get('style', ''))
                    for _ in range(cs):
                        if len(col_widths) <= ci + _:
                            col_widths.append(width if _ == 0 else None)
                
                row.append(txt)
                
                # 병합 영역 표시
                for dr in range(rs):
                    for dc in range(cs):
                        if dr > 0 or dc > 0:
                            occupied[(ri + dr, ci + dc)] = True
                
                # colspan 빈 셀 추가
                for _ in range(cs - 1):
                    row.append("")
                ci += cs
            
            rows_data.append(row)
            max_cols = max(max_cols, len(row))
        
        # 행/열 수 맞추기
        for row in rows_data:
            while len(row) < max_cols:
                row.append("")
        while len(col_widths) < max_cols:
            col_widths.append(None)
        
        rc = len(rows_data)
        if rc == 0 or max_cols == 0:
            return
        
        print(f"    표: {rc}행 × {max_cols}열")
        
        # ═══ 2. 열 너비 계산 (내용 길이 기반) ═══
        body_width_mm = 170  # A4 본문 폭 (210mm - 좌우 여백 40mm)
        
        # 지정된 너비가 있는 열 확인
        specified_width = sum(w for w in col_widths if w is not None)
        unspecified_indices = [i for i, w in enumerate(col_widths) if w is None]
        
        if unspecified_indices:
            # 각 열의 최대 텍스트 길이 계산 (한글=2, 영문/숫자=1)
            col_text_lengths = [0] * max_cols
            for row in rows_data:
                for ci, cell_text in enumerate(row):
                    if ci < max_cols:
                        # 한글은 2배 너비로 계산
                        length = sum(2 if ord(c) > 127 else 1 for c in str(cell_text))
                        col_text_lengths[ci] = max(col_text_lengths[ci], length)
            
            # 최소 너비 보장 (8자 이상)
            col_text_lengths = [max(length, 8) for length in col_text_lengths]
            
            # 미지정 열들의 총 텍스트 길이
            unspecified_total_length = sum(col_text_lengths[i] for i in unspecified_indices)
            
            # 남은 너비를 텍스트 길이 비율로 분배
            remaining_width = max(body_width_mm - specified_width, 15 * len(unspecified_indices))
            
            for i in unspecified_indices:
                if unspecified_total_length > 0:
                    ratio = col_text_lengths[i] / unspecified_total_length
                    col_widths[i] = remaining_width * ratio
                else:
                    col_widths[i] = remaining_width / len(unspecified_indices)
            
            print(f"    텍스트 길이: {col_text_lengths}")
        
        # 본문 폭 초과 시 비례 축소
        total = sum(col_widths)
        if total > body_width_mm:
            ratio = body_width_mm / total
            col_widths = [w * ratio for w in col_widths]
        
        col_widths_mm = [round(w, 1) for w in col_widths]
        print(f"    열 너비(mm): {col_widths_mm}")
        
        # ═══ 3. HWPX 후처리용 열 너비 저장 ═══
        self.table_widths.append(col_widths_mm)
        print(f"    📊 표 #{len(self.table_widths)} 저장 완료")
        
        # ═══ 4. HWP 표 생성 (기본 방식) ═══
        self._set_para('left', 130, before=5, after=0)
        self.hwp.create_table(rc, max_cols, treat_as_char=True)
        
        # ═══ 5. 셀 내용 입력 ═══
        for ri, row in enumerate(rows_data):
            for ci in range(max_cols):
                # 병합된 셀 건너뛰기
                if (ri, ci) in occupied:
                    self.hwp.HAction.Run("MoveRight")
                    continue
                
                txt = row[ci] if ci < len(row) else ""
                style = cell_styles.get((ri, ci), {})
                hdr = style.get('is_header', False)
                
                # 배경색
                if hdr:
                    self._set_cell_bg('#E8F5E9')
                elif style.get('bg_color'):
                    self._set_cell_bg(style['bg_color'])
                
                # 정렬
                align = style.get('align', 'center' if hdr else 'left')
                if align == 'center':
                    self.hwp.HAction.Run("ParagraphShapeAlignCenter")
                elif align == 'right':
                    self.hwp.HAction.Run("ParagraphShapeAlignRight")
                else:
                    self.hwp.HAction.Run("ParagraphShapeAlignLeft")
                
                # 폰트
                self._set_font(9 if hdr else 9.5, hdr, '#006400' if hdr else '#333333')
                self.hwp.insert_text(str(txt))
                
                # 다음 셀로 이동 (마지막 셀 제외)
                if not (ri == rc - 1 and ci == max_cols - 1):
                    self.hwp.HAction.Run("MoveRight")
        
        # ═══ 6. 표 편집 종료 ═══
        self.hwp.HAction.Run("Cancel")
        self.hwp.HAction.Run("CloseEx")
        self.hwp.HAction.Run("MoveDocEnd")
        self._set_para('left', 130, before=5, after=5)
        self.hwp.BreakPara()
    
    # ═══════════════════════════════════════════════════════════════
    # 이미지 삽입 - sizeoption 수정 ★
    # ═══════════════════════════════════════════════════════════════
    def _insert_image(self, src, caption=""):
        self.image_count += 1
        
        if not src:
            return
        
        # 🆕 assets 폴더에서 먼저 찾기
        filename = os.path.basename(src)
        full_path = os.path.join(self.cfg.ASSETS_PATH, filename)
        
        # assets에 없으면 기존 방식으로 fallback
        if not os.path.exists(full_path):
            if not os.path.isabs(src):
                full_path = os.path.normpath(os.path.join(self.base_path, src))
            else:
                full_path = src
        
        print(f"    📷 이미지 #{self.image_count}: {filename}")
        
        if not os.path.exists(full_path):
            print(f"       ❌ 파일 없음: {full_path}")
            self._set_font(9, False, '#999999')
            self._set_para('center', 130)
            self.hwp.insert_text(f"[이미지 없음: {os.path.basename(src)}]")
            self.hwp.BreakPara()
            return
        
        try:
            self._set_para('center', 130, before=5, after=3)
            
            # ★ sizeoption=0: 원본 크기
            # ★ sizeoption=2: 지정 크기 (width, height 필요)
            # ★ 둘 다 안되면 sizeoption 없이 시도
            
            inserted = False
            
            # 방법 1: sizeoption=0 (원본 크기)
            try:
                self.hwp.insert_picture(full_path, sizeoption=0)
                inserted = True
                print(f"       ✅ 삽입 성공 (원본 크기)")
            except Exception as e1:
                pass
            
            # 방법 2: width/height 지정
            if not inserted and HAS_PIL:
                try:
                    with Image.open(full_path) as img:
                        w_px, h_px = img.size
                    # px → mm 변환 (96 DPI 기준)
                    w_mm = w_px * 25.4 / 96
                    h_mm = h_px * 25.4 / 96
                    # 최대 너비 제한
                    if w_mm > self.cfg.MAX_IMAGE_WIDTH:
                        ratio = self.cfg.MAX_IMAGE_WIDTH / w_mm
                        w_mm = self.cfg.MAX_IMAGE_WIDTH
                        h_mm = h_mm * ratio
                    
                    self.hwp.insert_picture(full_path, sizeoption=1, 
                                           width=self._mm(w_mm), height=self._mm(h_mm))
                    inserted = True
                    print(f"       ✅ 삽입 성공 ({w_mm:.0f}×{h_mm:.0f}mm)")
                except Exception as e2:
                    pass
            
            # 방법 3: 기본값
            if not inserted:
                try:
                    self.hwp.insert_picture(full_path)
                    inserted = True
                    print(f"       ✅ 삽입 성공 (기본)")
                except Exception as e3:
                    print(f"       ❌ 삽입 실패: {e3}")
                    self._set_font(9, False, '#FF0000')
                    self.hwp.insert_text(f"[이미지 오류: {os.path.basename(src)}]")
            
            self.hwp.BreakPara()
            
            if caption and inserted:
                self._set_font(9.5, True, '#666666')
                self._set_para('center', 130, before=0, after=5)
                self.hwp.insert_text(caption)
                self.hwp.BreakPara()
                
        except Exception as e:
            print(f"       ❌ 오류: {e}")

    def _insert_table_from_element(self, elem: 'StyledElement'):
        """StyledElement에서 표 삽입 (수정됨)"""
        table_data = elem.attributes.get('table_data', {})
        if not table_data:
            return
        
        rows = table_data.get('rows', [])
        if not rows:
            return
        
        num_rows = len(rows)
        num_cols = max(len(row) for row in rows) if rows else 1
        
        print(f"  → 표 삽입: {num_rows}행 × {num_cols}열")
        
        try:
            # 1. 표 앞에 문단 설정
            self._set_para('left', 130, before=5, after=0)
            
            # 2. 표 생성 (pyhwpx 내장 메서드 사용)
            self.hwp.create_table(num_rows, num_cols, treat_as_char=True)
            
            # 3. 셀별 데이터 입력
            for row_idx, row in enumerate(rows):
                for col_idx, cell in enumerate(row):
                    # 셀 건너뛰기 (병합된 셀)
                    if col_idx >= len(row):
                        self.hwp.HAction.Run("TableRightCell")
                        continue
                    
                    cell_text = cell.get('text', '')
                    is_header = cell.get('is_header', False)
                    
                    # 헤더 셀 스타일
                    if is_header:
                        self._set_cell_bg('#E8F5E9')
                        self.hwp.HAction.Run("ParagraphShapeAlignCenter")
                        self._set_font(9, True, '#006400')
                    else:
                        self._set_font(9.5, False, '#333333')
                    
                    # 텍스트 입력
                    self.hwp.insert_text(cell_text)
                    
                    # 다음 셀로 (마지막 셀 제외)
                    if not (row_idx == num_rows - 1 and col_idx == num_cols - 1):
                        self.hwp.HAction.Run("TableRightCell")
            
            # 4. ★ 표 빠져나오기 (핵심!)
            self.hwp.HAction.Run("Cancel")        # 선택 해제
            self.hwp.HAction.Run("CloseEx")       # 표 편집 종료
            self.hwp.HAction.Run("MoveDocEnd")    # 문서 끝으로
            
            # 5. 표 뒤 문단
            self._set_para('left', 130, before=5, after=5)
            self.hwp.BreakPara()
            
            print(f"    ✅ 표 삽입 완료")
            
        except Exception as e:
            print(f"    [오류] 표 삽입 실패: {e}")
            # 표 안에 갇혔을 경우 탈출 시도
            try:
                self.hwp.HAction.Run("Cancel")
                self.hwp.HAction.Run("CloseEx")
                self.hwp.HAction.Run("MoveDocEnd")
            except:
                pass

    def _move_to_cell(self, row: int, col: int):
        """표에서 특정 셀로 이동"""
        # 첫 셀로 이동
        self.hwp.HAction.Run("TableColBegin")
        self.hwp.HAction.Run("TableRowBegin")
        
        # row만큼 아래로
        for _ in range(row):
            self.hwp.HAction.Run("TableLowerCell")
        
        # col만큼 오른쪽으로
        for _ in range(col):
            self.hwp.HAction.Run("TableRightCell")

    def _apply_cell_style(self, bold=False, bg_color=None, align='left'):
        """현재 셀 스타일 적용"""
        # 글자 굵기
        if bold:
            self.hwp.HAction.Run("CharShapeBold")
        
        # 정렬
        align_actions = {
            'left': "ParagraphShapeAlignLeft",
            'center': "ParagraphShapeAlignCenter",
            'right': "ParagraphShapeAlignRight",
        }
        if align in align_actions:
            self.hwp.HAction.Run(align_actions[align])
        
        # 배경색
        if bg_color:
            self._apply_cell_bg(bg_color)

    def _apply_cell_bg(self, color: str):
        """셀 배경색 적용"""
        try:
            color = color.lstrip('#')
            r, g, b = int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16)
            
            self.hwp.HAction.GetDefault("CellBorder", self.hwp.HParameterSet.HCellBorderFill.HSet)
            self.hwp.HParameterSet.HCellBorderFill.FillAttr.FillType = 1  # 단색
            self.hwp.HParameterSet.HCellBorderFill.FillAttr.WinBrush.FaceColor = self.hwp.RGBColor(r, g, b)
            self.hwp.HAction.Execute("CellBorder", self.hwp.HParameterSet.HCellBorderFill.HSet)
        except Exception as e:
            print(f"    [경고] 셀 배경색: {e}")


    def _insert_highlight_box(self, elem):
        txt = elem.get_text(strip=True)
        if not txt: return
        self._set_para('left', 130, before=5, after=0)
        self.hwp.create_table(1, 1, treat_as_char=True)
        self._set_cell_bg('#E2ECE2')
        self._set_font(11, False, '#333333')
        self.hwp.insert_text(txt)
        self.hwp.HAction.Run("Cancel")
        self.hwp.HAction.Run("CloseEx")
        self.hwp.HAction.Run("MoveDocEnd")
        self._set_para('left', 130, before=0, after=5)
        self.hwp.BreakPara()
    
    def _process(self, elem):
        if isinstance(elem, NavigableString): return
        tag = elem.name
        if not tag or tag in ['script','style','template','noscript','head']: return
        
        if tag == 'figure':
            img = elem.find('img')
            if img:
                figcaption = elem.find('figcaption')
                caption = figcaption.get_text(strip=True) if figcaption else ""
                self._insert_image(img.get('src', ''), caption)
            return
        
        if tag == 'img':
            self._insert_image(elem.get('src', ''))
            return
        
        if tag in ['h1','h2','h3']: self._insert_heading(elem)
        elif tag == 'p': self._insert_paragraph(elem)
        elif tag == 'table': self._insert_table(elem)
        elif tag in ['ul','ol']: self._insert_list(elem)
        elif 'highlight-box' in elem.get('class',[]): self._insert_highlight_box(elem)
        elif tag in ['div','section','article','main','body','html','span']:
            for ch in elem.children: self._process(ch)
    
    def convert(self, html_path, output_path):
        print("="*60)
        print("HTML → HWP 변환기 v11")
        print("  ✓ 이미지: sizeoption 수정")
        print("  ✓ 페이지번호: 다중 방법 시도")
        print("="*60)
        
        self.base_path = os.path.dirname(os.path.abspath(html_path))
        self.is_first_h1 = True
        self.image_count = 0
        self.table_widths = []  # 🆕 표 열 너비 초기화
       
        print(f"\n입력: {html_path}")
        print(f"출력: {output_path}\n")
        
        with open(html_path, 'r', encoding='utf-8') as f:
            soup = BeautifulSoup(f.read(), 'html.parser')
        
        title_tag = soup.find('title')
        if title_tag:
            full_title = title_tag.get_text(strip=True)
            footer_title = full_title.split(':')[0].strip()  # ":" 이전
        else:
            footer_title = ""

        self.hwp.FileNew()
        self._setup_page()
        self._create_footer(footer_title)

        raw = soup.find(id='raw-container')
        if raw:
            cover = raw.find(id='box-cover')
            if cover:
                print("  → 표지")
                for ch in cover.children: self._process(ch)
                self.hwp.HAction.Run("BreakPage")
            toc = raw.find(id='box-toc')
            if toc:
                print("  → 목차")
                self.is_first_h1 = True
                self._underline_box("목 차", 20, '#008000')
                self.hwp.BreakPara(); self.hwp.BreakPara()
                self._insert_list(toc.find('ul') or toc)
                self.hwp.HAction.Run("BreakPage")
            summary = raw.find(id='box-summary')
            if summary:
                print("  → 요약")
                self.is_first_h1 = True
                self._process(summary)
                self.hwp.HAction.Run("BreakPage")
            content = raw.find(id='box-content')
            if content:
                print("  → 본문")
                self.is_first_h1 = True
                self._process(content)
        else:
            self._process(soup.find('body') or soup)
        
        self.hwp.SaveAs(output_path)
        print(f"\n✅ 저장: {output_path}")
        print(f"   이미지: {self.image_count}개 처리")
    
    def convert_with_styles(self, html_path, output_path, sty_path=None):
        """
        스타일 그루핑이 적용된 HWP 변환 (하이브리드 방식)
        
        워크플로우:
        1. HTML 분석 (역할 분류)
        2. 기존 convert() 로직으로 HWP 생성 (표/이미지 정상 작동)
        3. .hwpx로 저장
        4. HWPX 후처리: 커스텀 스타일 주입
        """
        print("="*60)
        print("HTML → HWP 변환기 v11 (스타일 그루핑)")
        print("="*60)
        
        self.base_path = os.path.dirname(os.path.abspath(html_path))
        
        # ═══ 1단계: HTML 분석 ═══
        with open(html_path, 'r', encoding='utf-8') as f:
            html_content = f.read()
        
        analyzer = StyleAnalyzer()
        elements = analyzer.analyze(html_content)
        
        print(f"  🔧 HTML 전처리 중...")
        print(f"  📄 분석 완료: {len(elements)}개 요소")
        for role, count in analyzer.get_role_summary().items():
            print(f"   {role}: {count}")
        
        # ═══ 2단계: 기존 convert() 로직으로 HWP 생성 ═══
        # (표/이미지/머리말/꼬리말 모두 정상 작동)
        self.convert(html_path, output_path)
        
        # ═══ 3단계: .hwpx로 다시 저장 ═══
        hwpx_path = output_path.replace('.hwp', '.hwpx')
        if not hwpx_path.endswith('.hwpx'):
            hwpx_path = output_path + 'x'
        
        # HWP 다시 열어서 HWPX로 저장
        self.hwp.Open(output_path)
        self.hwp.SaveAs(hwpx_path, "HWPX")
        self.hwp.Clear(1)  # 문서 닫기
        
        print(f"\n  📦 HWPX 변환: {hwpx_path}")
        
        # ═══ 4단계: HWPX 후처리 - 스킵 (convert에서 이미 완성) ═══
        print(f"  ⏭️ 스타일 후처리 스킵 (convert 결과 유지)")
        
        # 🆕 ═══ 4-1단계: 표 열 너비 수정 ═══
        if self.table_widths:
            try:
                from converters.hwpx_table_injector import inject_table_widths
                inject_table_widths(hwpx_path, self.table_widths)
            except Exception as e:
                print(f"  [경고] 표 열 너비 수정 실패: {e}")
                import traceback
                traceback.print_exc()
        
        # ═══ 5단계: 최종 출력 ═══
        # HWPX를 기본 출력으로 사용 (또는 HWP로 재변환)
        final_output = hwpx_path
        
        print(f"\n✅ 최종 저장: {final_output}")
        return final_output

    def _get_style_config(self, role: str) -> dict:
        """역할에 따른 스타일 설정 반환"""
        
        STYLE_CONFIGS = {
            # 표지
            'COVER_TITLE': {'font_size': 32, 'bold': True, 'align': 'center', 'color': '#1a365d', 'space_before': 20, 'space_after': 10},
            'COVER_SUBTITLE': {'font_size': 18, 'bold': False, 'align': 'center', 'color': '#555555'},
            'COVER_INFO': {'font_size': 12, 'align': 'center', 'color': '#666666'},
            
            # 목차
            'TOC_H1': {'font_size': 12, 'bold': True, 'indent_left': 0},
            'TOC_H2': {'font_size': 11, 'indent_left': 5},
            'TOC_H3': {'font_size': 10, 'indent_left': 10, 'color': '#666666'},
            
            # 제목 계층
            'H1': {'font_size': 20, 'bold': True, 'align': 'left', 'color': '#008000', 'space_before': 15, 'space_after': 8},
            'H2': {'font_size': 16, 'bold': True, 'align': 'left', 'color': '#03581d', 'space_before': 12, 'space_after': 6},
            'H3': {'font_size': 13, 'bold': True, 'align': 'left', 'color': '#228B22', 'space_before': 10, 'space_after': 5},
            'H4': {'font_size': 12, 'bold': True, 'align': 'left', 'indent_left': 3, 'space_before': 8, 'space_after': 4},
            'H5': {'font_size': 11, 'bold': True, 'align': 'left', 'indent_left': 6, 'space_before': 6, 'space_after': 3},
            'H6': {'font_size': 11, 'bold': False, 'align': 'left', 'indent_left': 9},
            'H7': {'font_size': 10.5, 'bold': False, 'align': 'left', 'indent_left': 12},
            
            # 본문
            'BODY': {'font_size': 11, 'align': 'justify', 'line_height': 180, 'indent_first': 3},
            'LIST_ITEM': {'font_size': 11, 'align': 'left', 'indent_left': 5},
            'HIGHLIGHT_BOX': {'font_size': 10.5, 'align': 'left', 'indent_left': 3},
            
            # 표
            'TH': {'font_size': 9, 'bold': True, 'align': 'center', 'color': '#006400'},
            'TD': {'font_size': 9.5, 'align': 'left'},
            'TABLE_CAPTION': {'font_size': 10, 'bold': True, 'align': 'center'},
            
            # 그림
            'FIGURE': {'align': 'center'},
            'FIGURE_CAPTION': {'font_size': 9.5, 'align': 'center', 'color': '#666666'},
            
            # 기타
            'UNKNOWN': {'font_size': 11, 'align': 'left'},
        }
        
        return STYLE_CONFIGS.get(role, STYLE_CONFIGS['UNKNOWN'])

    def close(self):
        try: self.hwp.Quit()
        except: pass

def main():
    html_path = r"D:\for python\survey_test\output\generated\report.html"
    output_path = r"D:\for python\survey_test\output\generated\report_styled.hwp"
    sty_path = r"D:\for python\survey_test\교통영향평가스타일.sty"  # 🆕 추가
    
    try:
        conv = HtmlToHwpConverter(visible=True)
        conv.convert_with_styles(html_path, output_path, sty_path)  # 🆕 sty_path 추가
        input("\nEnter를 누르면 HWP가 닫힙니다...")
        conv.close()
    except Exception as e:
        print(f"\n[에러] {e}")
        import traceback; traceback.print_exc()

if __name__ == "__main__":
    main()