# -*- coding: utf-8 -*-
"""
템플릿 관리자 (Template Manager) v5.2
- 템플릿의 독립적 CRUD (생성/조회/삭제/교체)
- 문서 유형(DocType)과 분리된 저장 구조
- HWPX에서 템플릿 추출 → templates/user/templates/{tpl_id}/ 에 저장

★ v5.2 변경:
  - _build_body_html() 재설계: content_order 기반 본문 생성
    → 문단·이미지·표를 원본 순서 그대로 HTML 조립
    → content_order 없으면 기존 legacy 방식 자동 fallback
  - _build_title_block_html() 분리 (재사용성)

★ 저장 구조:
templates/user/
├── doc_types/{type_id}/
│     ├── config.json          ← 유형 정보 (맥락/구조/가이드)
│     └── template_id: "tpl_xxx"  ← 어떤 템플릿 참조하는지
│
└── templates/{tpl_id}/
      ├── template.html        ← HTML 골격 + placeholder
      ├── style.json           ← 테두리/폰트/색상/여백/borderFill
      └── meta.json            ← 이름, 출처, 생성일

★ 사용 흐름:
1) "템플릿 추가"   → extract_and_save(hwpx_path, name) → tpl_id
2) "문서 유형 추가" → doc_type_analyzer가 내부적으로 extract_and_save 호출
3) "템플릿 교체"   → change_template(type_id, new_tpl_id)
4) "문서 생성"     → load_template(tpl_id) → template.html + style.json
"""

import json
import time
import shutil
from pathlib import Path
from typing import Optional


class TemplateManager:
    """템플릿 독립 관리"""
    
    # 기본 경로
    TEMPLATES_USER = Path('templates/user/templates')
    TEMPLATES_DEFAULT = Path('templates/default/templates')
    DOC_TYPES_USER = Path('templates/user/doc_types')
    
    def __init__(self, base_path: str = None):
        if base_path:
            self.TEMPLATES_USER = Path(base_path) / 'user' / 'templates'
            self.TEMPLATES_DEFAULT = Path(base_path) / 'default' / 'templates'
            self.DOC_TYPES_USER = Path(base_path) / 'user' / 'doc_types'
    
    # ================================================================
    #  핵심 API
    # ================================================================
    
    def extract_and_save(self, parsed: dict, name: str, 
                         source_file: str = "", description: str = "") -> dict:
        """
        HWPX 파싱 결과에서 템플릿 추출 후 저장
        
        Args:
            parsed: HWPX 파서 결과 (raw_xml, tables, section_xml, header_xml, footer_xml)
            name: 템플릿 이름 (예: "GPD 발표기획서 양식")
            source_file: 원본 파일명
            description: 설명
            
        Returns:
            {"success": True, "template_id": "tpl_xxx", "path": "...", "template_info": {...}}
        """
        from .doc_template_analyzer import DocTemplateAnalyzer
        
        try:
            analyzer = DocTemplateAnalyzer()
            
            # ① 구조 추출 (template_info)
            template_info = analyzer.analyze(parsed)
            
            # ①-b semantic_map 생성 (표 역할 분류, 섹션 감지)
            from . import semantic_mapper
            semantic_map = semantic_mapper.generate(template_info, parsed)
            
            # ② HTML 생성 (semantic_map으로 표 필터링)
            template_html = self._generate_basic_html(template_info, parsed, semantic_map)
       
            # 저장
            tpl_id = f"tpl_{int(time.time())}"
            tpl_path = self.TEMPLATES_USER / tpl_id
            tpl_path.mkdir(parents=True, exist_ok=True)
            
            # template.html
            (tpl_path / 'template.html').write_text(template_html, encoding='utf-8')
            
            # style.json (template_info + 추출된 스타일)
            style_data = {
                "version": "v4",
                "source": "doc_template_analyzer",
                "template_info": template_info,
                "css": "",          # 추후 커스텀 CSS 오버라이드용
                "fonts": {},
                "colors": self._extract_colors(template_info),
                "border_fills": template_info.get("border_fills", {}),
                "tables": [],
                "style_summary": {}
            }
            (tpl_path / 'style.json').write_text(
                json.dumps(style_data, ensure_ascii=False, indent=2), 
                encoding='utf-8'
            )
            
            # meta.json
            meta = {
                "id": tpl_id,
                "name": name,
                "original_file": source_file,
                "file_type": Path(source_file).suffix if source_file else ".hwpx",
                "description": description,
                "features": self._summarize_features(template_info, semantic_map),
                "created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
                "source": "doc_template_analyzer"
            }
            (tpl_path / 'meta.json').write_text(
                json.dumps(meta, ensure_ascii=False, indent=2),
                encoding='utf-8'
            )
            
            # semantic_map.json
            (tpl_path / 'semantic_map.json').write_text(
                json.dumps(semantic_map, ensure_ascii=False, indent=2),
                encoding='utf-8'
            )

            return {
                "success": True,
                "template_id": tpl_id,
                "path": str(tpl_path),
                "template_info": template_info,
                "semantic_map": semantic_map,
                "meta": meta
            }
            
        except Exception as e:
            import traceback
            return {"error": str(e), "trace": traceback.format_exc()}
    
    def load_template(self, tpl_id: str) -> dict:
        """
        템플릿 로드 (template.html + style.json)
        
        Returns:
            {"html": "...", "style": {...}, "meta": {...}}
        """
        # 사용자 템플릿 → 기본 템플릿 순서로 탐색
        for base in [self.TEMPLATES_USER, self.TEMPLATES_DEFAULT]:
            tpl_path = base / tpl_id
            if tpl_path.exists():
                result = {}
                
                html_file = tpl_path / 'template.html'
                if html_file.exists():
                    result["html"] = html_file.read_text(encoding='utf-8')
                
                style_file = tpl_path / 'style.json'
                if style_file.exists():
                    result["style"] = json.loads(style_file.read_text(encoding='utf-8'))
                
                meta_file = tpl_path / 'meta.json'
                if meta_file.exists():
                    result["meta"] = json.loads(meta_file.read_text(encoding='utf-8'))
                
                result["template_id"] = tpl_id
                result["path"] = str(tpl_path)
                return result
        
        return {"error": f"템플릿을 찾을 수 없습니다: {tpl_id}"}
    
    def list_templates(self) -> list:
        """모든 템플릿 목록 조회"""
        templates = []
        
        for base, is_default in [(self.TEMPLATES_DEFAULT, True), (self.TEMPLATES_USER, False)]:
            if not base.exists():
                continue
            for folder in sorted(base.iterdir()):
                if not folder.is_dir():
                    continue
                meta_file = folder / 'meta.json'
                if meta_file.exists():
                    try:
                        meta = json.loads(meta_file.read_text(encoding='utf-8'))
                        meta["is_default"] = is_default
                        templates.append(meta)
                    except:
                        templates.append({
                            "id": folder.name,
                            "name": folder.name,
                            "is_default": is_default
                        })
        
        return templates
    
    def delete_template(self, tpl_id: str) -> dict:
        """템플릿 삭제 (사용자 템플릿만)"""
        tpl_path = self.TEMPLATES_USER / tpl_id
        
        if not tpl_path.exists():
            return {"error": f"템플릿을 찾을 수 없습니다: {tpl_id}"}
        
        # 이 템플릿을 참조하는 DocType이 있는지 확인
        referencing = self._find_referencing_doc_types(tpl_id)
        if referencing:
            names = ', '.join(r['name'] for r in referencing[:3])
            return {
                "error": f"이 템플릿을 사용 중인 문서 유형이 있습니다: {names}",
                "referencing_types": referencing
            }
        
        shutil.rmtree(tpl_path)
        return {"success": True, "deleted": tpl_id}
    
    def change_template(self, type_id: str, new_tpl_id: str) -> dict:
        """
        문서 유형의 템플릿 교체
        
        Args:
            type_id: 문서 유형 ID
            new_tpl_id: 새 템플릿 ID
        """
        config_path = self.DOC_TYPES_USER / type_id / 'config.json'
        
        if not config_path.exists():
            return {"error": f"문서 유형을 찾을 수 없습니다: {type_id}"}
        
        # 새 템플릿 존재 확인
        new_tpl = self.load_template(new_tpl_id)
        if "error" in new_tpl:
            return new_tpl
        
        # config 업데이트
        config = json.loads(config_path.read_text(encoding='utf-8'))
        old_tpl_id = config.get("template_id", "")
        config["template_id"] = new_tpl_id
        config["updatedAt"] = time.strftime("%Y-%m-%dT%H:%M:%SZ")
        
        config_path.write_text(
            json.dumps(config, ensure_ascii=False, indent=2),
            encoding='utf-8'
        )
        
        return {
            "success": True,
            "type_id": type_id,
            "old_template_id": old_tpl_id,
            "new_template_id": new_tpl_id
        }
    
    def get_template_for_doctype(self, type_id: str) -> dict:
        """문서 유형에 연결된 템플릿 로드"""
        config_path = self.DOC_TYPES_USER / type_id / 'config.json'
        
        if not config_path.exists():
            # default에서도 탐색
            config_path = self.TEMPLATES_DEFAULT.parent / 'doc_types' / type_id / 'config.json'
        
        if not config_path.exists():
            return {"error": f"문서 유형을 찾을 수 없습니다: {type_id}"}
        
        config = json.loads(config_path.read_text(encoding='utf-8'))
        tpl_id = config.get("template_id")
        
        if not tpl_id:
            # ★ 하위 호환: template_id가 없으면 같은 폴더의 template.html 사용
            legacy_path = config_path.parent / 'template.html'
            if legacy_path.exists():
                return {
                    "html": legacy_path.read_text(encoding='utf-8'),
                    "style": {},
                    "meta": {"id": type_id, "name": "레거시 템플릿"},
                    "template_id": None,
                    "legacy": True
                }
            return {"error": "연결된 템플릿이 없습니다"}
        
        return self.load_template(tpl_id)
    
    # ================================================================
    #  내부 유틸
    # ================================================================
    
    def _find_referencing_doc_types(self, tpl_id: str) -> list:
        """특정 템플릿을 참조하는 DocType 목록"""
        result = []
        if not self.DOC_TYPES_USER.exists():
            return result
        
        for folder in self.DOC_TYPES_USER.iterdir():
            config_file = folder / 'config.json'
            if config_file.exists():
                try:
                    config = json.loads(config_file.read_text(encoding='utf-8'))
                    if config.get("template_id") == tpl_id:
                        result.append({
                            "id": config.get("id", folder.name),
                            "name": config.get("name", folder.name)
                        })
                except:
                    pass
        return result


    def _generate_basic_html(self, template_info: dict, parsed: dict,
                             semantic_map: dict = None) -> str:
        """tools 추출 결과 + style_generator → template.html 생성"""
        # ① CSS 생성 (style_generator)
        from . import style_generator
        css = style_generator.generate_css(template_info, semantic_map)

        # ② 헤더 HTML
        header_html = self._build_header_html(template_info.get("header"))

        # ③ 푸터 HTML
        footer_html = self._build_footer_html(template_info.get("footer"))

        # ④ 본문 HTML (섹션 + 표)
        body_html = self._build_body_html(template_info, parsed, semantic_map)

        # ⑤ 조립
        html = f"""<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Template</title>
<style>
{css}
</style>
</head>
<body>
<div class="page">

{header_html}

{body_html}

{footer_html}

</div>
</body>
</html>"""
        return html

    # ── 보조 메서드들 ──
    def _build_header_html(self, header_info: dict | None) -> str:
        """header tools 추출값 → HTML + placeholder"""
        if not header_info or not header_info.get("exists"):
            return "<!-- no header -->"
        
        html = '<div class="doc-header">\n'
        
        if header_info.get("type") == "table" and header_info.get("table"):
            tbl = header_info["table"]
            rows = tbl.get("rows", [])
            col_pcts = tbl.get("colWidths_pct", [])
            
            # ★ 추가: colWidths_pct 없으면 셀 width_hu에서 계산
            if not col_pcts and rows:
                widths = [c.get("width_hu", 0) for c in rows[0]]
                total = sum(widths)
                if total > 0:
                    col_pcts = [round(w / total * 100) for w in widths]
            
            html += '<table>\n'
            if col_pcts:
                html += '<colgroup>\n'
                for pct in col_pcts:
                    html += f'  <col style="width:{pct}%">\n'
                html += '</colgroup>\n'
            
            for r_idx, row in enumerate(rows):
                html += '<tr>\n'
                for c_idx, cell in enumerate(row):
                    lines = cell.get("lines", [])
                    cell_text = cell.get("text", "").strip()   # ★ 추가
                    ph_name = f"HEADER_R{r_idx+1}_C{c_idx+1}"
                    
                    # ★ 수정: 텍스트 없는 셀은 비움
                    if not cell_text and not lines:
                        content = ""
                    elif len(lines) > 1:
                        # 멀티라인 셀 → 각 라인별 placeholder
                        line_phs = []
                        for l_idx in range(len(lines)):
                            line_phs.append(f"{{{{{ph_name}_LINE_{l_idx+1}}}}}")
                        content = "<br>".join(line_phs)
                    else:
                        content = f"{{{{{ph_name}}}}}"
                    
                    # colSpan/rowSpan
                    attrs = ""
                    bf_ref = cell.get("borderFillIDRef")
                    if bf_ref:
                        attrs += f' class="bf-{bf_ref}"'
                    if cell.get("colSpan", 1) > 1:
                        attrs += f' colspan="{cell["colSpan"]}"'
                    if cell.get("rowSpan", 1) > 1:
                        attrs += f' rowspan="{cell["rowSpan"]}"'
                    
                    html += f'  <td{attrs}>{content}</td>\n'
                html += '</tr>\n'
            
            html += '</table>\n'
        else:
            # 텍스트형 헤더
            texts = header_info.get("texts", [])
            for i in range(max(len(texts), 1)):
                html += f'  <div>{{{{{f"HEADER_TEXT_{i+1}"}}}}}</div>\n'
        
        html += '</div>'
        return html

    def _build_footer_html(self, footer_info: dict | None) -> str:
        """footer tools 추출값 → HTML + placeholder"""
        if not footer_info or not footer_info.get("exists"):
            return "<!-- no footer -->"
        
        html = '<div class="doc-footer">\n'
        
        if footer_info.get("type") == "table" and footer_info.get("table"):
            tbl = footer_info["table"]
            rows = tbl.get("rows", [])
            col_pcts = tbl.get("colWidths_pct", [])
            
            # ★ 추가: colWidths_pct 없으면 셀 width_hu에서 계산
            if not col_pcts and rows:
                widths = [c.get("width_hu", 0) for c in rows[0]]
                total = sum(widths)
                if total > 0:
                    col_pcts = [round(w / total * 100) for w in widths]
            
            html += '<table>\n'
            if col_pcts:
                html += '<colgroup>\n'
                for pct in col_pcts:
                    html += f'  <col style="width:{pct}%">\n'
                html += '</colgroup>\n'
            
            for r_idx, row in enumerate(rows):
                html += '<tr>\n'
                for c_idx, cell in enumerate(row):
                    lines = cell.get("lines", [])
                    cell_text = cell.get("text", "").strip()   # ★ 추가
                    ph_name = f"FOOTER_R{r_idx+1}_C{c_idx+1}"

                    
                    # ★ 수정: 텍스트 없는 셀은 비움
                    if not cell_text and not lines:
                        content = ""
                    elif len(lines) > 1:
                        line_phs = []
                        for l_idx in range(len(lines)):
                            line_phs.append(f"{{{{{ph_name}_LINE_{l_idx+1}}}}}")
                        content = "<br>".join(line_phs)
                    else:
                        content = f"{{{{{ph_name}}}}}"
                    
                    attrs = ""
                    bf_ref = cell.get("borderFillIDRef")
                    if bf_ref:
                        attrs += f' class="bf-{bf_ref}"'
                    if cell.get("colSpan", 1) > 1:
                        attrs += f' colspan="{cell["colSpan"]}"'
                    if cell.get("rowSpan", 1) > 1:
                        attrs += f' rowspan="{cell["rowSpan"]}"'
                    
                    html += f'  <td{attrs}>{content}</td>\n'
                html += '</tr>\n'
            
            html += '</table>\n'
        else:
            # 텍스트형 푸터 — 일반적으로 페이지 번호
            html += '  <div style="text-align:center">{{PAGE_NUMBER}}</div>\n'
        
        html += '</div>'
        return html

    def _build_body_html(self, template_info: dict, parsed: dict,
                         semantic_map: dict = None) -> str:
        """본문 영역 HTML 생성.

        ★ v5.2: content_order가 있으면 원본 순서 그대로 조립.
        content_order 없으면 기존 섹션+표 방식 (하위 호환).
        """
        content_order = template_info.get("content_order")

        if content_order and self._has_paragraph_content(content_order):
            return self._build_body_from_content_order(
                template_info, content_order, semantic_map
            )
        else:
            return self._build_body_legacy(
                template_info, parsed, semantic_map
            )

    # ── content_order 기반 본문 생성 (v5.2+) ──

    def _has_paragraph_content(self, content_order: list) -> bool:
        """content_order에 문단이 있는지 (표만 있으면 legacy 사용)"""
        return any(
            c['type'] == 'paragraph' for c in content_order
        )

    def _build_body_from_content_order(self, template_info: dict,
                                        content_order: list,
                                        semantic_map: dict = None) -> str:
        """content_order 기반 — 원본 문서 순서 그대로 HTML 조립.

        콘텐츠 유형별 처리:
          paragraph → <p class="ppr-{id}"><span class="cpr-{id}">{{CONTENT_n}}</span></p>
          table     → data-table placeholder (title_table 제외)
          image     → <div class="img-wrap">{{IMAGE_n}}</div>
          empty     → 생략 (연속 빈 문단 의미 없음)
        """
        import re

        tables = template_info.get("tables", [])

        # semantic_map에서 title/body 인덱스
        title_table_idx = None
        body_table_indices = []
        if semantic_map:
            title_table_idx = semantic_map.get("title_table")
            body_table_indices = semantic_map.get("body_tables", [])
        else:
            body_table_indices = [t["index"] for t in tables]

        # ★ v5.3: content_order table_idx → tables 리스트 매핑
        # content_order.table_idx = section body에서 만난 표 순번 (0-based)
        # tables 리스트 = HWPX 전체 표 (header/footer 포함)
        # → header/footer 제외한 "본문 가시 표" 리스트로 매핑해야 정확함
        header_footer_indices = set()
        if semantic_map:
            for idx_key, role_info in semantic_map.get("table_roles", {}).items():
                role = role_info.get("role", "")
                if role in ("header_table", "footer_table"):
                    try:
                        header_footer_indices.add(int(idx_key))
                    except (ValueError, TypeError):
                        pass

        body_visible_tables = [
            t for t in tables
            if t["index"] not in header_footer_indices
        ]

        body_parts = []

        # ── 제목 블록 (title_table이 있으면) ──
        if title_table_idx is not None:
            title_tbl = next(
                (t for t in tables if t["index"] == title_table_idx), None
            )
            if title_tbl:
                body_parts.append(
                    self._build_title_block_html(title_tbl)
                )

        # ── content_order 순회 ──
        para_num = 0    # 문단 placeholder 번호
        tbl_num = 0     # 데이터 표 번호 (1-based)
        img_num = 0     # 이미지 placeholder 번호
        in_section = False
        section_num = 0

        # 섹션 제목 패턴
        sec_patterns = [
            re.compile(r'^\d+\.\s+\S'),
            re.compile(r'^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]\.\s*\S'),
            re.compile(r'^제\s*\d+\s*[장절항]\s*\S'),
        ]

        def _is_section_title(text: str) -> bool:
            return any(p.match(text) for p in sec_patterns)

        for item in content_order:
            itype = item['type']

            # ── 빈 문단: 생략 ──
            if itype == 'empty':
                continue

            # ── 표: title_table은 이미 처리, body_table만 ──
            # table_idx = content_order.py가 부여한 등장순서 0-based
            # ★ v5.3: body_visible_tables로 매핑 (header/footer 표 제외)
            if itype == 'table':
                t_idx = item.get('table_idx', 0)
                # body_visible_tables에서 해당 인덱스의 표 가져오기
                if t_idx < len(body_visible_tables):
                    tbl_data = body_visible_tables[t_idx]
                    if tbl_data["index"] == title_table_idx:
                        continue  # title_table 건너뛰기
                    if tbl_data["index"] not in body_table_indices:
                        continue  # body 데이터 표가 아니면 건너뛰기

                tbl_num += 1
                col_cnt = item.get('colCnt', '3')
                try:
                    col_cnt = int(col_cnt)
                except (ValueError, TypeError):
                    col_cnt = 3

                # semantic_map에서 col_headers 가져오기
                _roles = semantic_map.get("table_roles", {}) if semantic_map else {}
                if t_idx < len(body_visible_tables):
                    tbl_data = body_visible_tables[t_idx]
                    tbl_role = _roles.get(tbl_data["index"],
                                          _roles.get(str(tbl_data["index"]), {}))
                    col_headers = tbl_role.get("col_headers", [])
                    actual_col_cnt = len(col_headers) if col_headers else col_cnt

                    rows = tbl_data.get("rows", [])
                    header_row_data = rows[0] if rows else None
                    col_pcts = tbl_data.get("colWidths_pct", [])
                else:
                    actual_col_cnt = col_cnt
                    header_row_data = None
                    col_pcts = []

                body_parts.append(
                    self._build_table_placeholder(
                        tbl_num, actual_col_cnt, col_pcts,
                        header_row=header_row_data
                    )
                )
                continue

            # ── 이미지 ──
            if itype == 'image':
                img_num += 1
                ppr = item.get('paraPrIDRef', '0')
                caption = item.get('text', '')
                ref = item.get('binaryItemIDRef', '')

                img_html = f'<div class="img-wrap ppr-{ppr}">\n'
                img_html += f'  {{{{IMAGE_{img_num}}}}}\n'
                if caption:
                    img_html += f'  <p class="img-caption">{{{{IMAGE_{img_num}_CAPTION}}}}</p>\n'
                img_html += '</div>'
                body_parts.append(img_html)
                continue

            # ── 문단 ──
            if itype == 'paragraph':
                text = item.get('text', '')
                ppr = item.get('paraPrIDRef', '0')
                cpr = item.get('charPrIDRef', '0')

                # 섹션 제목 감지
                if _is_section_title(text):
                    # 이전 섹션 닫기
                    if in_section:
                        body_parts.append('</div>\n')

                    section_num += 1
                    in_section = True
                    body_parts.append(
                        f'<div class="section" data-section="{section_num}">\n'
                        f'  <p class="section-title ppr-{ppr} cpr-{cpr}">'
                        f'{{{{SECTION_{section_num}_TITLE}}}}</p>'
                    )
                    continue

                # 일반 문단
                para_num += 1

                # runs가 여러 개면 다중 span
                runs = item.get('runs', [])
                if len(runs) > 1:
                    spans = []
                    for r_idx, run in enumerate(runs):
                        r_cpr = run.get('charPrIDRef', cpr)
                        spans.append(
                            f'<span class="cpr-{r_cpr}">'
                            f'{{{{PARA_{para_num}_RUN_{r_idx+1}}}}}</span>'
                        )
                    inner = ''.join(spans)
                else:
                    inner = (
                        f'<span class="cpr-{cpr}">'
                        f'{{{{PARA_{para_num}}}}}</span>'
                    )

                body_parts.append(
                    f'<p class="ppr-{ppr}">{inner}</p>'
                )

        # 마지막 섹션 닫기
        if in_section:
            body_parts.append('</div>\n')

        return "\n\n".join(body_parts)

    def _build_title_block_html(self, title_tbl: dict) -> str:
        """제목표 → title-block HTML (기존 로직 분리)"""
        rows = title_tbl.get("rows", [])
        col_pcts = title_tbl.get("colWidths_pct", [])

        html = '<div class="title-block">\n<table class="title-table">\n'

        if col_pcts:
            html += '<colgroup>\n'
            for pct in col_pcts:
                html += f'  <col style="width:{pct}%">\n'
            html += '</colgroup>\n'

        for r_idx, row in enumerate(rows):
            html += '<tr>\n'
            for c_idx, cell in enumerate(row):
                attrs = ""
                bf_ref = cell.get("borderFillIDRef")
                if bf_ref:
                    attrs += f' class="bf-{bf_ref}"'
                cs = cell.get("colSpan", 1)
                if cs > 1:
                    attrs += f' colspan="{cs}"'
                rs = cell.get("rowSpan", 1)
                if rs > 1:
                    attrs += f' rowspan="{rs}"'

                cell_text = cell.get("text", "").strip()
                if cell_text:
                    ph_name = f"TITLE_R{r_idx+1}_C{c_idx+1}"
                    html += f'  <td{attrs}>{{{{{ph_name}}}}}</td>\n'
                else:
                    html += f'  <td{attrs}></td>\n'
            html += '</tr>\n'

        html += '</table>\n</div>\n'
        return html

    # ── 기존 섹션+표 방식 (하위 호환) ──

    def _build_body_legacy(self, template_info: dict, parsed: dict,
                            semantic_map: dict = None) -> str:
        """content_order 없을 때 — 기존 v5.1 방식 유지"""
        body_parts = []
        tables = template_info.get("tables", [])
        
        # ── semantic_map이 있으면 활용 ──
        if semantic_map:
            body_table_indices = semantic_map.get("body_tables", [])
            title_idx = semantic_map.get("title_table")
        else:
            # semantic_map 없으면 전체 표 사용 (하위 호환)
            body_table_indices = [t["index"] for t in tables]
            title_idx = None

        # ── 제목 블록 ──
        if title_idx is not None:
            title_tbl = next((t for t in tables if t["index"] == title_idx), None)
            if title_tbl:
                body_parts.append(self._build_title_block_html(title_tbl))

        # ── 본문 데이터 표만 필터링 ──
        body_tables = [t for t in tables if t["index"] in body_table_indices]
        
        # ── 섹션 감지 ──
        section_titles = self._detect_section_titles(parsed)
        
        if not section_titles and not body_tables:
            # 구조 정보 부족 → 기본 1섹션
            body_parts.append(
                '<div class="section">\n'
                '  <div class="section-title">{{SECTION_1_TITLE}}</div>\n'
                '  <div class="section-content">{{SECTION_1_CONTENT}}</div>\n'
                '</div>'
            )
        else:
            sec_count = max(len(section_titles), 1)
            tbl_idx = 0
            
            for s in range(sec_count):
                s_num = s + 1
                body_parts.append(
                    f'<div class="section">\n'
                    f'  <div class="section-title">{{{{SECTION_{s_num}_TITLE}}}}</div>\n'
                    f'  <div class="section-content">{{{{SECTION_{s_num}_CONTENT}}}}</div>\n'
                )
                
                # 이 섹션에 표 배분
                if tbl_idx < len(body_tables):
                    t = body_tables[tbl_idx]
                    col_cnt = t.get("colCnt", 3)
                    
                    # semantic_map에서 실제 col_headers 가져오기
                    _roles = semantic_map.get("table_roles", {}) if semantic_map else {}
                    tbl_role = _roles.get(t["index"], _roles.get(str(t["index"]), {}))
                    col_headers = tbl_role.get("col_headers", [])
                    actual_col_cnt = len(col_headers) if col_headers else col_cnt
                    
                    # 헤더행 셀 데이터 (bf_id 포함)
                    rows = t.get("rows", [])
                    header_row_data = rows[0] if rows else None

                    body_parts.append(
                        self._build_table_placeholder(
                            tbl_idx + 1, actual_col_cnt,
                            t.get("colWidths_pct", []),
                            header_row=header_row_data    # ★ 헤더행 전달
                        )
                    )
                    tbl_idx += 1
                
                body_parts.append('</div>\n')
            
            # 남은 표
            while tbl_idx < len(body_tables):
                t = body_tables[tbl_idx]
                col_cnt = t.get("colCnt", 3)
                _roles = semantic_map.get("table_roles", {}) if semantic_map else {}
                tbl_role = _roles.get(t["index"], _roles.get(str(t["index"]), {}))
                col_headers = tbl_role.get("col_headers", [])
                actual_col_cnt = len(col_headers) if col_headers else col_cnt
                rows = t.get("rows", [])
                header_row_data = rows[0] if rows else None
                body_parts.append(
                    self._build_table_placeholder(
                        tbl_idx + 1, actual_col_cnt,
                        t.get("colWidths_pct", []),
                        header_row=header_row_data
                    )
                )
                tbl_idx += 1
        
        return "\n".join(body_parts)

    def _build_table_placeholder(self, tbl_num: int, col_cnt: int,
                                col_pcts: list = None,
                                header_row: list = None) -> str:
        """표 1개의 placeholder HTML 생성

        Args:
            tbl_num: 표 번호 (1-based)
            col_cnt: 열 수
            col_pcts: 열 너비 % 리스트
            header_row: 헤더행 셀 리스트 [{bf_id, colSpan, ...}, ...]
        """
        # colgroup
        colgroup = ""
        num_cols = len(col_pcts) if col_pcts else col_cnt
        if num_cols > 0:
            colgroup = "<colgroup>\n"
            if col_pcts and len(col_pcts) == num_cols:
                for pct in col_pcts:
                    colgroup += f'  <col style="width:{pct}%">\n'
            else:
                for _ in range(num_cols):
                    colgroup += "  <col>\n"
            colgroup += "</colgroup>\n"

        # 헤더 행 — ★ bf_id가 있으면 class 적용
        header_cells = []
        if header_row:
            for c, cell in enumerate(header_row):
                bf_id = cell.get("borderFillIDRef")
                cs = cell.get("colSpan", 1)

                attrs = ""
                if bf_id:
                    attrs += f' class="bf-{bf_id}"'
                if cs > 1:
                    attrs += f' colspan="{cs}"'

                header_cells.append(
                    f'    <th{attrs}>{{{{TABLE_{tbl_num}_H_C{c+1}}}}}</th>'
                )
        else:
            # fallback: bf 없는 경우
            for c in range(col_cnt):
                header_cells.append(
                    f'    <th>{{{{TABLE_{tbl_num}_H_C{c+1}}}}}</th>'
                )

        header_row_html = "\n".join(header_cells)

        return (
            f'<table class="data-table tbl-{tbl_num}">\n'
            f'{colgroup}'
            f'<thead>\n'
            f'  <tr>\n{header_row_html}\n  </tr>\n'
            f'</thead>\n'
            f'<tbody>\n'
            f'  {{{{TABLE_{tbl_num}_BODY}}}}\n'
            f'</tbody>\n'
            f'</table>'
        )

    def _detect_section_titles(self, parsed: dict) -> list:
        """parsed 텍스트에서 섹션 제목 패턴 탐색"""
        import re
        titles = []
        
        # parsed에서 텍스트 추출
        paragraphs = parsed.get("paragraphs", [])
        if not paragraphs:
            # raw_xml에서 <hp:t> 태그 텍스트 추출 시도
            section_xml = ""
            raw_xml = parsed.get("raw_xml", {})
            for key, val in raw_xml.items():
                if "section" in key.lower():
                    section_xml = val if isinstance(val, str) else ""
                    break
            if not section_xml:
                section_xml = parsed.get("section_xml", "")
            
            if section_xml:
                t_matches = re.findall(r'<hp:t>([^<]+)</hp:t>', section_xml)
                paragraphs = [t.strip() for t in t_matches if t.strip()]
        
        # 섹션 제목 패턴
        patterns = [
            r'^(\d+)\.\s+\S',           # "1. 제목"
            r'^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]\.\s*\S',  # "Ⅰ. 제목"
            r'^제\s*\d+\s*[장절항]\s*\S',   # "제1장 제목"
        ]
        
        for text in paragraphs:
            if isinstance(text, dict):
                text = text.get("text", "")
            text = str(text).strip()
            if not text:
                continue
            for pat in patterns:
                if re.match(pat, text):
                    titles.append(text)
                    break
        
        return titles

    def _extract_colors(self, template_info: dict) -> dict:
        """template_info에서 색상 정보 추출"""
        colors = {"background": [], "border": [], "text": []}
        
        bf = template_info.get("border_fills", {})
        for fill_id, fill_data in bf.items():
            # ★ background 키 사용 (bg → background)
            bg = fill_data.get("background", fill_data.get("bg", ""))
            if bg and bg.lower() not in ("", "none", "transparent") \
               and bg not in colors["background"]:
                colors["background"].append(bg)
            
            # ★ css dict에서 border 색상 추출
            css_dict = fill_data.get("css", {})
            for prop, val in css_dict.items():
                if "border" in prop and val and val != "none":
                    # "0.12mm solid #999999" → "#999999"
                    parts = val.split()
                    if len(parts) >= 3:
                        c = parts[-1]
                        if c.startswith("#") and c not in colors["border"]:
                            colors["border"].append(c)
            
            # fallback: 직접 side 키 (top/bottom/left/right)
            for side_key in ("top", "bottom", "left", "right"):
                side = fill_data.get(side_key, {})
                if isinstance(side, dict):
                    c = side.get("color", "")
                    if c and c not in colors["border"]:
                        colors["border"].append(c)
        
        return colors
    
    def _summarize_features(self, template_info: dict,
                                semantic_map: dict = None) -> list:
        """template_info에서 특징 요약"""
        features = []
        
        header = template_info.get("header", {})
        footer = template_info.get("footer", {})
        tables = template_info.get("tables", [])
        
        # 폰트 (fonts 구조: {"HANGUL": [{"face": "맑은 고딕"}], ...})
        fonts = template_info.get("fonts", {})
        hangul = fonts.get("HANGUL", [])
        if hangul and isinstance(hangul, list) and len(hangul) > 0:
            features.append(f"폰트: {hangul[0].get('face', '?')}")
        
        # 머릿말 (header.table.colCnt)
        if header.get("exists"):
            col_cnt = header.get("table", {}).get("colCnt", "?")
            features.append(f"머릿말: {col_cnt}열")
        
        # 꼬릿말 (footer.table.colCnt)
        if footer.get("exists"):
            col_cnt = footer.get("table", {}).get("colCnt", "?")
            features.append(f"꼬릿말: {col_cnt}열")
        
        # 표 — semantic_map이 있으면 데이터 표만
        if semantic_map and semantic_map.get("body_tables"):
            for idx in semantic_map["body_tables"]:
                t = next((tb for tb in tables if tb["index"] == idx), None)
                if t:
                    features.append(
                        f"표: {t.get('rowCnt', '?')}x{t.get('colCnt', '?')}"
                    )
        elif tables:
            t = tables[0]
            features.append(f"표: {t.get('rowCnt', '?')}x{t.get('colCnt', '?')}")
        
        return features