# -*- coding: utf-8 -*-
"""
문서 유형 분석 Agent (v3.1 - 코드 기반 레이아웃 추출, hwpx_utils 연동)

★ v3.1 변경사항 (from v3.0):
- 레이아웃 추출: AI 호출 제거 → 코드 기반 (HWPX 파싱 데이터에서 직접 도출)
  · headerLayout: 실제 헤더 테이블 colCount/셀텍스트 그대로 반영
  · footerLayout: 실제 푸터 테이블 구조 그대로 반영
  · sections: 문단 텍스트 패턴 매칭으로 추출
  · overallStyle: 불릿/표 사용 패턴 코드 분석
- 단위 변환: hwpx_utils 연동 (hwpunit_to_mm, charsize_to_pt)
- AI는 맥락(목적/문서유형)과 구조가이드(섹션별 작성법)에만 사용
- headerLayout 할루시네이션 완전 제거
- _generate_html_template (AI fallback) 제거 — template_manager가 전담
- _parse_tables_in_region: <hp:tc> 태그 속성에서도 병합/너비 추출 (도메인 가이드 §6.3)

참조:
- domain/hwpx/hwpx_domain_guide.md (§1~§11)
- domain/hwpx/hwpx_utils.py (변환 함수/상수)
"""

import zipfile
import json
import time
import re
import sys
import os

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from .template_manager import TemplateManager
from pathlib import Path
from typing import Dict, List, Any, Optional
from .common import call_claude, extract_json

# ================================================================
#  hwpx_utils 연동 (fallback 포함)
# ================================================================
try:
    from domain.hwpx.hwpx_utils import (
        hwpunit_to_mm, charsize_to_pt, mm_format, detect_paper_size
    )
except ImportError:
    def hwpunit_to_mm(hu): return hu / 7200 * 25.4
    def charsize_to_pt(s): return s / 100
    def mm_format(hu, d=1): return f'{hwpunit_to_mm(hu):.{d}f}mm'
    def detect_paper_size(w, h, t=200): return 'A4'


class DocTypeAnalyzer:
    """문서 유형 분석 Orchestrator (v3.1 - 코드 기반 레이아웃)"""

    def __init__(self, progress_callback=None):
        self.progress_callback = progress_callback
        self.template_manager = TemplateManager()
        self.steps = [
            {"id": 1, "name": "문서 파싱", "status": "pending"},
            {"id": 2, "name": "레이아웃 추출", "status": "pending"},
            {"id": 3, "name": "맥락 분석", "status": "pending"},
            {"id": 4, "name": "구조 분석", "status": "pending"},
            {"id": 5, "name": "템플릿 추출", "status": "pending"},
            {"id": 6, "name": "최종 검증", "status": "pending"},
        ]

    def _update_step(self, step_id: int, status: str, message: str = ""):
        for step in self.steps:
            if step["id"] == step_id:
                step["status"] = status
                break
        if self.progress_callback:
            self.progress_callback(step_id, status, message)

    # ================================================================
    #  메인 분석 플로우
    # ================================================================

    def analyze(self, file_path: str, doc_name: str,
                description: str = "") -> dict:
        """전체 분석 실행

        Flow:
            Step 1: HWPX 파싱 (코드)
            Step 2: 레이아웃 추출 (코드) — AI 미사용
            Step 3: 맥락 분석 (AI) — 문서 유형/목적/대상 판정
            Step 4: 구조 분석 (AI) — 섹션별 작성 가이드 생성
            Step 5: 템플릿 추출 (template_manager → doc_template_analyzer)
            Step 6: config.json 생성
        """
        result = {"name": doc_name, "description": description}

        # Step 1: HWPX 파싱
        self._update_step(1, "running", "HWPX 파일 분석 중...")
        parsed = self._parse_hwpx(file_path)
        result["parsed"] = parsed
        self._update_step(
            1, "done",
            f"텍스트 {len(parsed.get('text', ''))}자, "
            f"표 {len(parsed.get('tables', []))}개")

        # Step 2: 레이아웃 추출 (★ 코드 기반, AI 미사용)
        self._update_step(2, "running", "문서 레이아웃 구조 추출 중...")
        layout = self._extract_layout(parsed)
        result["layout"] = layout
        self._update_step(
            2, "done",
            f"헤더:{layout.get('hasHeader')}, "
            f"푸터:{layout.get('hasFooter')}, "
            f"섹션:{len(layout.get('sections', []))}개")

        # Step 3: 맥락 분석 (AI - 의미 분석만)
        self._update_step(3, "running", "문서의 목적/배경 분석 중...")
        context = self._analyze_context(parsed, layout)
        result["context"] = context
        self._update_step(
            3, "done",
            f"문서 유형: {context.get('documentType', '알 수 없음')}")

        # Step 4: 구조 분석 (AI - 섹션별 작성 가이드)
        self._update_step(4, "running", "문서 구조 상세 분석 중...")
        structure = self._analyze_structure(parsed, layout, context)
        result["structure"] = structure
        style = self._extract_style(parsed)
        result["style"] = style
        self._update_step(4, "done", "구조 분석 완료")

        # Step 5: 템플릿 추출 (template_manager 전담)
        self._update_step(5, "running", "HTML 템플릿 추출 중...")
        try:
            tpl_result = self.template_manager.extract_and_save(
                parsed,
                name=f"{doc_name} 양식",
                source_file=os.path.basename(file_path),
                description=f"{doc_name}에서 추출한 문서 양식"
            )
            print(f"[DEBUG] tpl_result keys: {list(tpl_result.keys())}")
            print(f"[DEBUG] tpl_result keys: {list(tpl_result.keys())}")
            print(f"[DEBUG] tpl_result: {tpl_result}")
        except Exception as e:
            import traceback
            print(f"[ERROR] Step 5 실패: {e}")
            traceback.print_exc()
            tpl_result = {}

        # ★ template_id를 result로 전달                     ← 추가!
        if tpl_result.get("success"):
            result["template_id"] = tpl_result["template_id"]

        # Step 5-b: content_prompt 생성 (doc_type 소속)       ← ★ 이 블록 추가
        content_prompt = {}
        if tpl_result.get("success"):
            try:
                from . import content_analyzer
                content_prompt = content_analyzer.generate(
                    tpl_result["template_info"],
                    tpl_result["semantic_map"],
                    parsed
                )
                result["content_prompt"] = content_prompt
            except Exception as e:
                print(f"[WARN] content_prompt 생성 오류: {e}")

        # Step 6: Config 생성
        self._update_step(6, "running", "최종 설정 파일 생성 중...")
        config = self._generate_config(doc_name, description, result)
        result["config"] = config
        self._update_step(6, "done", "분석 완료!")

        return result

    # ================================================================
    #  Step 1: HWPX 파싱
    # ================================================================

    def _parse_hwpx(self, file_path: str) -> dict:
        """HWPX 완전 파싱 - XML 구조 보존"""
        content = {
            "text": "",
            "raw_xml": {},
            "paragraphs": [],
            "tables": [],
            "images": [],
            "header_xml": "",
            "footer_xml": "",
            "section_xml": "",
        }

        with zipfile.ZipFile(file_path, 'r') as zf:
            for name in zf.namelist():
                if name.endswith('.xml'):
                    try:
                        xml_content = zf.read(name).decode('utf-8')
                        content["raw_xml"][name] = xml_content

                        if 'section' in name.lower():
                            content["section_xml"] = xml_content
                            content["text"] = self._extract_all_text(
                                xml_content)
                            content["paragraphs"] = (
                                self._extract_paragraphs(xml_content))
                            content["tables"] = (
                                self._extract_tables_detailed(xml_content))
                            content["page_header_xml"] = self._extract_header(xml_content)
                            content["page_footer_xml"] = self._extract_footer(xml_content)
                            content["images"] = self._extract_images(
                                xml_content)
                    except Exception as e:
                        print(f"[WARN] XML 파싱 오류: {name} - {e}")

        return content

    def _extract_all_text(self, xml: str) -> str:
        """모든 텍스트 추출"""
        texts = re.findall(r'<hp:t>([^<]*)</hp:t>', xml)
        return ' '.join(texts)

    def _extract_paragraphs(self, xml: str) -> List[dict]:
        """문단별 구조 추출"""
        paragraphs = []
        p_pattern = re.compile(r'<hp:p[^>]*>(.*?)</hp:p>', re.DOTALL)

        for match in p_pattern.finditer(xml):
            p_content = match.group(1)
            texts = re.findall(r'<hp:t>([^<]*)</hp:t>', p_content)
            text = ' '.join(texts).strip()

            if not text:
                continue

            style_ref = re.search(r'styleIDRef="(\d+)"', match.group(0))
            char_ref = re.search(r'charPrIDRef="(\d+)"', p_content)
            has_image = '<hp:pic' in p_content

            paragraphs.append({
                "text": text,
                "styleRef": style_ref.group(1) if style_ref else None,
                "charRef": char_ref.group(1) if char_ref else None,
                "hasImage": has_image,
                "raw": p_content[:500]
            })

        return paragraphs

    def _extract_tables_detailed(self, xml: str) -> List[dict]:
        """표 상세 구조 추출 - 영역별 분리"""
        tables = []

        # header 영역
        header_xml = self._extract_header(xml)
        for tbl in self._parse_tables_in_region(header_xml):
            tbl["location"] = "header"
            tbl["isLayoutTable"] = True
            tables.append(tbl)

        # footer 영역
        footer_xml = self._extract_footer(xml)
        for tbl in self._parse_tables_in_region(footer_xml):
            tbl["location"] = "footer"
            tbl["isLayoutTable"] = True
            tables.append(tbl)

        # 본문 (header/footer 제거)
        body_xml = re.sub(
            r'<hp:header[^>]*>.*?</hp:header>', '',
            xml, flags=re.DOTALL)
        body_xml = re.sub(
            r'<hp:footer[^>]*>.*?</hp:footer>', '',
            body_xml, flags=re.DOTALL)

        for tbl in self._parse_tables_in_region(body_xml):
            tbl["location"] = "body"
            tbl["isLayoutTable"] = self._is_layout_table(tbl["cells"])
            tables.append(tbl)

        return tables

    def _parse_tables_in_region(self, xml: str) -> List[dict]:
        """특정 영역 내 테이블 파싱

        ★ v3.1: <hp:tc> 태그 속성에서도 colSpan/rowSpan/width 추출.
        도메인 가이드 §6.3: <hp:tc colAddr="0" rowAddr="0"
            colSpan="2" width="17008" borderFillIDRef="3">
        """
        tables = []
        tbl_pattern = re.compile(
            r'<hp:tbl[^>]*>(.*?)</hp:tbl>', re.DOTALL)

        for match in tbl_pattern.finditer(xml):
            tbl_tag = match.group(0)
            tbl_content = match.group(1)

            row_cnt = re.search(r'rowCnt="(\d+)"', tbl_tag)
            col_cnt = re.search(r'colCnt="(\d+)"', tbl_tag)

            cells = []
            row_pattern = re.compile(
                r'<hp:tr>(.*?)</hp:tr>', re.DOTALL)

            for row_match in row_pattern.finditer(tbl_content):
                row_content = row_match.group(1)
                # ★ tc 태그 속성 + 내부 콘텐츠 모두 캡처
                tc_pattern = re.compile(
                    r'<hp:tc([^>]*)>(.*?)</hp:tc>', re.DOTALL)

                row_cells = []
                for cell_match in tc_pattern.finditer(row_content):
                    tc_attrs = cell_match.group(1)
                    cell_content = cell_match.group(2)

                    # 셀 내 문단별 텍스트
                    p_texts = []
                    cell_paras = re.findall(
                        r'<hp:p[^>]*>(.*?)</hp:p>',
                        cell_content, re.DOTALL)
                    for cp in cell_paras:
                        cp_text = ' '.join(
                            re.findall(r'<hp:t>([^<]*)</hp:t>', cp)
                        ).strip()
                        if cp_text:
                            p_texts.append(cp_text)

                    # ★ 1순위: <hp:tc> 태그 속성 (§6.3)
                    cs_m = re.search(r'colSpan="(\d+)"', tc_attrs)
                    rs_m = re.search(r'rowSpan="(\d+)"', tc_attrs)
                    w_m = re.search(r'width="(\d+)"', tc_attrs)

                    # 2순위: 자식 요소 fallback
                    if not cs_m:
                        cs_m = re.search(
                            r'<hp:cellSpan[^>]*colSpan="(\d+)"',
                            cell_content)
                    if not rs_m:
                        rs_m = re.search(
                            r'<hp:cellSpan[^>]*rowSpan="(\d+)"',
                            cell_content)
                    if not w_m:
                        w_m = re.search(
                            r'<hp:cellSz[^>]*width="(\d+)"',
                            cell_content)

                    row_cells.append({
                        "text": ' '.join(p_texts),
                        "lines": p_texts,
                        "colSpan": int(cs_m.group(1)) if cs_m else 1,
                        "rowSpan": int(rs_m.group(1)) if rs_m else 1,
                        "width": int(w_m.group(1)) if w_m else 0,
                    })

                if row_cells:
                    cells.append(row_cells)

            tables.append({
                "rowCount": (int(row_cnt.group(1))
                             if row_cnt else len(cells)),
                "colCount": (int(col_cnt.group(1))
                             if col_cnt else 0),
                "cells": cells,
            })

        return tables

    def _is_layout_table(self, cells: List[List]) -> bool:
        """본문 테이블 중 레이아웃 테이블(제목 블록 등) 판별"""
        if not cells or len(cells) != 1:
            return False

        row = cells[0]
        total_text = ' '.join(
            c["text"] if isinstance(c, dict) else str(c)
            for c in row)

        return len(row) <= 3 and len(total_text) < 200

    def _extract_header(self, xml: str) -> str:
        """머릿말 XML 추출"""
        m = re.search(
            r'<hp:header[^>]*>(.*?)</hp:header>', xml, re.DOTALL)
        return m.group(1) if m else ""

    def _extract_footer(self, xml: str) -> str:
        """꼬리말 XML 추출"""
        m = re.search(
            r'<hp:footer[^>]*>(.*?)</hp:footer>', xml, re.DOTALL)
        return m.group(1) if m else ""

    def _extract_images(self, xml: str) -> List[dict]:
        """이미지 참조 추출"""
        images = []
        pic_pattern = re.compile(
            r'<hp:pic[^>]*>(.*?)</hp:pic>', re.DOTALL)

        for match in pic_pattern.finditer(xml):
            img_ref = re.search(
                r'binaryItemIDRef="([^"]+)"', match.group(1))
            if img_ref:
                images.append({
                    "ref": img_ref.group(1),
                    "raw": match.group(0)[:300]
                })

        return images

    # ================================================================
    #  Step 2: 레이아웃 추출 (★ 코드 기반, AI 미사용)
    # ================================================================

    def _extract_layout(self, parsed: dict) -> dict:
        """코드 기반 레이아웃 추출 - HWPX 파싱 데이터에서 직접 도출

        ★ v3.1: AI 호출 완전 제거. 할루시네이션 없이 실제 HWPX 데이터만 반영.

        이전 v3.0 문제:
        - AI가 "columns": ["부서명", "문서번호", "작성일자"] 식으로 지어냄
        - hasHeader: True 하드코딩 (HWPX에 헤더 없어도)
        - left/center/right 3열 고정 스키마 강제

        v3.1 해결:
        - headerLayout.cellTexts: 실제 HWPX 셀 텍스트 그대로
        - hasHeader: bool(header_xml.strip()) 실제 존재 여부
        - colCount: 실제 열 수
        """
        tables = parsed.get("tables", [])
        paragraphs = parsed.get("paragraphs", [])
        section_xml = parsed.get("section_xml", "")
        header_xml = parsed.get("page_header_xml", "")
        footer_xml = parsed.get("page_footer_xml", "")

        header_tables = [t for t in tables
                         if t.get("location") == "header"]
        footer_tables = [t for t in tables
                         if t.get("location") == "footer"]
        body_tables = [t for t in tables
                       if t.get("location") == "body"]

        return {
            "hasHeader": bool(header_xml.strip()),
            "headerLayout": self._code_header_layout(
                header_xml, header_tables),
            "hasFooter": bool(footer_xml.strip()),
            "footerLayout": self._code_footer_layout(
                footer_xml, footer_tables),
            "titleBlock": self._code_title_block(body_tables),
            "sections": self._code_sections(section_xml, body_tables),
            "overallStyle": self._code_overall_style(
                paragraphs, body_tables),
        }

    def _code_header_layout(self, header_xml: str,
                            header_tables: list) -> dict:
        """헤더 레이아웃 — 실제 HWPX 데이터 그대로 반영

        ★ AI가 "좌측=부서명, 우측=날짜" 식으로 지어내던 것을
           실제 테이블 셀 텍스트로 대체.
        """
        if not header_xml.strip() and not header_tables:
            return {"structure": "없음"}

        if header_tables:
            ht = header_tables[0]
            cells = ht.get("cells", [])

            # 실제 셀 텍스트/줄 추출
            cell_texts = []
            cell_lines = []
            if cells:
                for row in cells:
                    for cell in row:
                        text = (cell.get("text", "")
                                if isinstance(cell, dict)
                                else str(cell))
                        lines = (cell.get("lines", [text])
                                 if isinstance(cell, dict)
                                 else [str(cell)])
                        cell_texts.append(text)
                        cell_lines.append(lines)

            return {
                "structure": "테이블",
                "colCount": (ht.get("colCount")
                             or (len(cells[0]) if cells else 0)),
                "rowCount": len(cells),
                "cellTexts": cell_texts,
                "cellLines": cell_lines,
            }

        # 텍스트만 있는 헤더
        texts = re.findall(r'<hp:t>([^<]*)</hp:t>', header_xml)
        return {
            "structure": "텍스트",
            "texts": texts,
        }

    def _code_footer_layout(self, footer_xml: str,
                            footer_tables: list) -> dict:
        """푸터 레이아웃 — 실제 HWPX 데이터 그대로 반영"""
        if not footer_xml.strip() and not footer_tables:
            return {"structure": "없음"}

        if footer_tables:
            ft = footer_tables[0]
            cells = ft.get("cells", [])

            cell_texts = []
            cell_lines = []
            if cells:
                for row in cells:
                    for cell in row:
                        text = (cell.get("text", "")
                                if isinstance(cell, dict)
                                else str(cell))
                        lines = (cell.get("lines", [text])
                                 if isinstance(cell, dict)
                                 else [str(cell)])
                        cell_texts.append(text)
                        cell_lines.append(lines)

            return {
                "structure": "테이블",
                "colCount": (ft.get("colCount")
                             or (len(cells[0]) if cells else 0)),
                "rowCount": len(cells),
                "cellTexts": cell_texts,
                "cellLines": cell_lines,
            }

        texts = re.findall(r'<hp:t>([^<]*)</hp:t>', footer_xml)
        return {
            "structure": "텍스트",
            "texts": texts,
        }

    def _code_title_block(self, body_tables: list) -> dict:
        """제목 블록 — 본문 첫 레이아웃 테이블에서 추출"""
        layout_tables = [t for t in body_tables
                         if t.get("isLayoutTable")]

        if layout_tables:
            lt = layout_tables[0]
            cells = lt.get("cells", [])
            text = ""
            if cells and cells[0]:
                text = ' '.join(
                    c.get("text", "") if isinstance(c, dict) else str(c)
                    for c in cells[0]
                ).strip()
            return {
                "type": "테이블",
                "colCount": (lt.get("colCount")
                             or (len(cells[0]) if cells else 1)),
                "text": text,
            }

        return {"type": "없음"}

    def _code_sections(self, section_xml: str,
                       body_tables: list) -> list:
        """섹션 추출 — section_xml에서 문단 패턴 매칭

        doc_template_analyzer.py v3.0과 동일한 패턴 감지:
            1. "1. 개요" (번호+점)
            2. <hp:pic> 아이콘 + 짧은 텍스트
            3. "Ⅰ.", "Ⅱ." (로마자)
            4. "제1장", "제2절" (한국식)
        """
        sections = []
        data_tables = [t for t in body_tables
                       if not t.get("isLayoutTable")]
        table_idx = 0
        current_section = None

        # header/footer 제거
        clean_xml = re.sub(
            r'<hp:header[^>]*>.*?</hp:header>', '',
            section_xml, flags=re.DOTALL)
        clean_xml = re.sub(
            r'<hp:footer[^>]*>.*?</hp:footer>', '',
            clean_xml, flags=re.DOTALL)

        paragraphs = re.findall(
            r'<hp:p[^>]*>(.*?)</hp:p>', clean_xml, re.DOTALL)

        for p_content in paragraphs:
            texts = re.findall(r'<hp:t>([^<]*)</hp:t>', p_content)
            text = ' '.join(texts).strip()
            if not text:
                continue

            is_section_title = False

            if re.match(r'^\d+\.\s+', text):
                is_section_title = True
            elif '<hp:pic' in p_content and len(text) < 30:
                is_section_title = True
            elif re.match(r'^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]\.\s*', text):
                is_section_title = True
            elif re.match(r'^제\d+[장절항]\s*', text):
                is_section_title = True

            if is_section_title:
                current_section = {
                    "name": text.strip(),
                    "hasBulletIcon": '<hp:pic' in p_content,
                    "hasTable": False,
                    "tableIndex": None,
                }
                sections.append(current_section)

            # 표 연결
            if '<hp:tbl' in p_content and table_idx < len(data_tables):
                if current_section:
                    current_section["hasTable"] = True
                    current_section["tableIndex"] = table_idx
                table_idx += 1

        return sections

    def _code_overall_style(self, paragraphs: list,
                            body_tables: list) -> dict:
        """전체 문체/불릿/표 사용 패턴 — 코드 기반 분석"""
        bullet_chars = {
            '·': 0, '▶': 0, '▷': 0, '●': 0,
            '■': 0, '-': 0, '•': 0, '○': 0,
        }
        bullet_total = 0
        prose_count = 0

        for p in paragraphs:
            text = p.get("text", "").strip()
            if not text:
                continue

            found_bullet = False
            for char in bullet_chars:
                if text.startswith(char) or text.startswith(f' {char}'):
                    bullet_chars[char] += 1
                    bullet_total += 1
                    found_bullet = True
                    break

            if not found_bullet and len(text) > 50:
                prose_count += 1

        # 가장 많이 사용된 불릿
        most_common = None
        if any(v > 0 for v in bullet_chars.values()):
            most_common = max(bullet_chars, key=bullet_chars.get)

        # 문체 판정
        if bullet_total > prose_count * 2:
            writing_style = "개조식"
        elif prose_count > bullet_total * 2:
            writing_style = "서술식"
        else:
            writing_style = "혼합"

        # 표 사용량
        data_tables = [t for t in body_tables
                       if not t.get("isLayoutTable")]
        table_count = len(data_tables)
        if table_count >= 3:
            table_usage = "많음"
        elif table_count >= 1:
            table_usage = "보통"
        else:
            table_usage = "없음"

        return {
            "writingStyle": writing_style,
            "bulletType": most_common or "·",
            "tableUsage": table_usage,
        }

    # ================================================================
    #  Step 3: 맥락 분석 (AI - 의미 분석만)
    # ================================================================

    def _analyze_context(self, parsed: dict, layout: dict) -> dict:
        """문서 맥락 분석 (목적, 대상, 톤)

        ★ v3.1: 물리적 구조는 코드 추출 완료 상태.
        AI에게는 텍스트+섹션명을 주고 의미(문서유형/목적)만 판단 요청.
        """
        text = parsed.get("text", "")[:4000]
        sections = layout.get("sections", [])
        section_names = [s.get("name", "") for s in sections]

        prompt = f"""당신은 문서 유형 분석 전문가입니다.

## 문서 텍스트 (일부)
{text}

## 문서에서 추출된 섹션 제목들
{json.dumps(section_names, ensure_ascii=False)}

## 🎯 핵심 과제
이 문서를 보자마자 **"아! 이건 OOO를 하기 위한 OOO 문서구나!"**라고
한 문장으로 정의하세요.

예시:
- "발표를 하기 위한 기획서"
- "프로젝트 승인을 받기 위한 제안서"
- "회의 결과를 공유하기 위한 회의록"
- "업무 현황을 보고하기 위한 보고서"

## ⛔ 주의
- 문서 안의 구체적 내용은 무시 (고유명사, 프로젝트명, 기술명 등)
- 문서의 **형식/목적/역할**만 파악

JSON으로 응답:
{{
    "documentDefinition": "OOO를 하기 위한 OOO 문서",
    "documentType": "문서 유형명 (기획서, 보고서, 제안서 등)",
    "purpose": "이 문서 형식의 목적",
    "perspective": "어떤 내용이 들어와도 이 관점으로 재구성해야 함",
    "audience": "일반적 대상 (상위 결재자, 팀원, 고객 등)",
    "tone": "톤 (보고형/제안형/공유형)"
}}"""

        try:
            response = call_claude(
                "문서 맥락 분석 전문가입니다.",
                prompt,
                max_tokens=1000
            )
            result = extract_json(response)
            if result:
                return result
        except Exception as e:
            print(f"[WARN] 맥락 분석 오류: {e}")

        return {
            "documentDefinition": "",
            "documentType": "일반 문서",
            "purpose": "",
            "perspective": "",
            "audience": "일반",
            "tone": "보고형",
        }

    # ================================================================
    #  Step 4: 구조 분석 (AI - 섹션별 작성 가이드)
    # ================================================================

    def _analyze_structure(self, parsed: dict, layout: dict,
                           context: dict) -> dict:
        """구조 상세 분석 - 섹션별 역할, 문체, 표 구조

        ★ v3.1: layout.sections가 코드 추출이므로 실제 섹션명 전달.
        AI는 각 섹션의 의미적 역할과 작성 가이드만 생성.
        """
        text = parsed.get("text", "")[:4000]
        tables = parsed.get("tables", [])
        sections = layout.get("sections", [])

        # 표 상세 정보 (본문 데이터 테이블만)
        table_details = []
        for i, t in enumerate(tables):
            if t.get("location") == "body" and not t.get("isLayoutTable"):
                cells = t.get("cells", [])
                headers = cells[0] if cells else []
                sample_rows = cells[1:3] if len(cells) > 1 else []

                table_details.append({
                    "index": i + 1,
                    "rows": t["rowCount"],
                    "cols": t["colCount"],
                    "headers": [
                        (c.get("text", "") if isinstance(c, dict)
                         else str(c))
                        for c in headers
                    ],
                    "sampleData": [
                        [(c.get("text", "")[:50] if isinstance(c, dict)
                          else str(c)[:50])
                         for c in row]
                        for row in sample_rows
                    ]
                })

        prompt = f"""당신은 문서 구조 분석 전문가입니다.

## 문서 유형
{context.get('documentDefinition', '문서')}

## 문서에서 추출된 섹션들
{json.dumps(sections, ensure_ascii=False, indent=2)}

## 본문 텍스트
{text}

## 표 상세 정보
{json.dumps(table_details, ensure_ascii=False, indent=2)}

## 🎯 분석 과제
각 섹션의 **역할과 작성 가이드**를 분석하세요.

### 필수 항목
1. **섹션명**: 정확한 섹션 제목
2. **역할**: 이 섹션이 문서에서 하는 역할
3. **문체**: 개조식 / 서술식
4. **표 포함 여부**: 표가 있으면 구조까지 상세히

### 표가 있는 섹션은 반드시:
- 몇 열인지, 각 열의 역할
- 각 열에 어떤 형태로 내용이 들어가는지

## ⛔ 주의
- 샘플 문서의 구체적 내용 언급 금지
- 섹션의 **역할과 형식**만 설명

JSON:
{{
    "sectionGuides": [
        {{
            "name": "섹션명",
            "role": "이 섹션의 역할",
            "writingStyle": "개조식/서술식",
            "contentGuide": "작성 가이드",
            "hasTable": false
        }},
        {{
            "name": "섹션명",
            "role": "역할",
            "writingStyle": "개조식",
            "contentGuide": "가이드",
            "hasTable": true,
            "tableStructure": {{
                "columns": 3,
                "columnDefs": [
                    {{"name": "열명", "role": "역할", "style": "스타일"}},
                    {{"name": "열명", "role": "역할", "style": "스타일"}},
                    {{"name": "열명", "role": "역할", "style": "스타일"}}
                ],
                "rowGuide": "각 행 설명"
            }}
        }}
    ],
    "writingPrinciples": [
        "전체 문서 작성 원칙1",
        "원칙2"
    ],
    "pageEstimate": 1
}}"""

        try:
            response = call_claude(
                "문서 구조 분석 전문가입니다. "
                "섹션별 역할과 표 구조를 상세히 분석합니다.",
                prompt,
                max_tokens=3000
            )
            result = extract_json(response)
            if result:
                return result
        except Exception as e:
            print(f"[WARN] 구조 분석 오류: {e}")

        return {
            "sectionGuides": [],
            "writingPrinciples": [],
            "pageEstimate": 1,
        }

    # ================================================================
    #  스타일 추출 (hwpx_utils 연동)
    # ================================================================

    def _extract_style(self, parsed: dict) -> dict:
        """스타일 추출 - XML 직접 파싱

        ★ v3.1 FIX:
        - 여백: hwpunit_to_mm() 사용
          이전 v3.0: int(val) / 100 → 5668→56.7mm (잘못됨)
          이후 v3.1: hwpunit_to_mm(5668) → 20.0mm (정확)
        - 폰트 크기: charsize_to_pt() 사용
        - 폰트 이름: fontface에서 실제 face 추출 (ID가 아닌 이름)
        - 도메인 가이드 §1, §3, §4, §7 참조
        """
        raw_xml = parsed.get("raw_xml", {})
        section_xml = parsed.get("section_xml", "")

        result = {
            "font": {"name": None, "size": None},
            "colors": {"primary": None, "secondary": None},
            "margins": {
                "top": None, "bottom": None,
                "left": None, "right": None},
            "lineHeight": None,
            "headingStyle": {"h1": None, "h2": None},
            "bulletStyle": None,
            "alignment": None,
        }

        for xml_name, xml_content in raw_xml.items():
            # ★ §7.2: 페이지 마진 (HWPUNIT → mm)
            for tag in ['margin', 'pageMargin']:
                # 속성 순서 무관 개별 추출
                t_m = re.search(
                    rf'<(?:\w+:)?{tag}\b[^>]*\btop="(\d+)"',
                    xml_content)
                if t_m:
                    b_m = re.search(
                        rf'<(?:\w+:)?{tag}\b[^>]*\bbottom="(\d+)"',
                        xml_content)
                    l_m = re.search(
                        rf'<(?:\w+:)?{tag}\b[^>]*\bleft="(\d+)"',
                        xml_content)
                    r_m = re.search(
                        rf'<(?:\w+:)?{tag}\b[^>]*\bright="(\d+)"',
                        xml_content)
                    result["margins"] = {
                        "top": mm_format(int(t_m.group(1))),
                        "bottom": (mm_format(int(b_m.group(1)))
                                   if b_m else "15.0mm"),
                        "left": (mm_format(int(l_m.group(1)))
                                 if l_m else "20.0mm"),
                        "right": (mm_format(int(r_m.group(1)))
                                  if r_m else "20.0mm"),
                    }
                    break

            # §3.2: 폰트 - fontface에서 실제 face 이름 추출
            if not result["font"]["name"]:
                font_match = re.search(
                    r'<(?:\w+:)?fontface[^>]*lang="HANGUL"[^>]*>.*?'
                    r'<(?:\w+:)?font[^>]*face="([^"]+)"',
                    xml_content, re.DOTALL)
                if font_match:
                    result["font"]["name"] = font_match.group(1)

                # fallback: fontRef hangul (이름이면 사용, ID면 무시)
                if not result["font"]["name"]:
                    fr_match = re.search(
                        r'<(?:\w+:)?fontRef[^>]*hangul="([^"]+)"',
                        xml_content)
                    if fr_match:
                        val = fr_match.group(1)
                        if not val.isdigit():
                            result["font"]["name"] = val

            # §4.1: 폰트 크기 (charPr height → pt)
            if not result["font"]["size"]:
                size_match = re.search(
                    r'<(?:\w+:)?charPr[^>]*height="(\d+)"',
                    xml_content)
                if size_match:
                    pt = charsize_to_pt(int(size_match.group(1)))
                    result["font"]["size"] = f"{pt:.0f}pt"

            # §1.3: 색상 (HWPX는 #RRGGBB)
            if not result["colors"]["primary"]:
                color_match = re.search(
                    r'\bcolor="(#[0-9a-fA-F]{6})"', xml_content)
                if color_match:
                    result["colors"]["primary"] = color_match.group(1)

        # 줄간격 (lineSpacing 속성)
        line_match = re.search(r'lineSpacing="(\d+)"', section_xml)
        if line_match:
            result["lineHeight"] = int(line_match.group(1))

        # 정렬 (align 속성)
        align_match = re.search(r'\balign="([A-Z]+)"', section_xml)
        if align_match:
            result["alignment"] = align_match.group(1)

        return result

    # ================================================================
    #  Step 6: Config 생성
    # ================================================================

    def _generate_config(self, doc_name: str, description: str,
                         result: dict) -> dict:
        """config.json 생성"""
        doc_id = f"user_{int(time.time())}"
        context = result.get("context", {})
        structure = result.get("structure", {})
        layout = result.get("layout", {})

        features = []
        features.append({
            "icon": "📋",
            "text": context.get("documentType", "문서")
        })

        purpose = context.get("purpose", "")
        purpose_short = (
            (purpose[:15] + "...") if len(purpose) > 15 else purpose)
        if purpose_short:
            features.append({"icon": "🎯", "text": purpose_short})

        features.append({
            "icon": "👥",
            "text": context.get("audience", "일반")
        })
        features.append({
            "icon": "📄",
            "text": f"약 {structure.get('pageEstimate', '?')}p"
        })

        return {
            "id": doc_id,
            "name": doc_name,
            "icon": "📄",
            "description": (description
                            or context.get("documentType", "")),
            "features": features[:4],
            "thumbnailType": "custom",
            "enabled": True,
            "isDefault": False,
            "order": 100,
            "template_id": result.get("template_id"),

            "context": {
                "documentDefinition": context.get(
                    "documentDefinition", ""),
                "documentType": context.get("documentType", ""),
                "purpose": context.get("purpose", ""),
                "perspective": context.get("perspective", ""),
                "audience": context.get("audience", ""),
                "tone": context.get("tone", ""),
            },

            "layout": layout,
            "structure": structure,
            "content_prompt": result.get("content_prompt", {}), 
            "options": {},

            "createdAt": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
            "updatedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
        }

    # ================================================================
    #  저장
    # ================================================================

    def save_doc_type(self, config: dict, template: str,
                      base_path: str = "templates/user/doc_types") -> str:
        """분석 결과 저장 (config.json — 템플릿은 template_manager가 관리)"""
        doc_path = Path(base_path) / config["id"]
        doc_path.mkdir(parents=True, exist_ok=True)

        config_path = doc_path / "config.json"
        with open(config_path, "w", encoding="utf-8") as f:
            json.dump(config, f, ensure_ascii=False, indent=2)

        # ★ content_prompt.json 저장                         ← 추가!
        content_prompt = config.pop("content_prompt", {})
        if content_prompt:
            with open(doc_path / "content_prompt.json", "w", encoding="utf-8") as f:
                json.dump(content_prompt, f, ensure_ascii=False, indent=2)

        # template_id 없는 경우(fallback)만 template.html 직접 저장
        if not config.get("template_id") and template:
            template_path = doc_path / "template.html"
            with open(template_path, "w", encoding="utf-8") as f:
                f.write(template)

        return str(doc_path)