# -*- coding: utf-8 -*-
"""
사용자 정의 문서 유형 프로세서 (v2.1 - 템플릿 기반)
- template.html 로드
- config.json의 구조/가이드 활용
- 사용자 입력 내용을 템플릿에 정리하여 채움
- 창작 X, 정리/재구성 O

★ v2.1 변경사항:
- 한글 포함 placeholder 지원 (TABLE_1_H_구분 등)
- TABLE_*_BODY / TABLE_*_H_* placeholder 구분 처리
- 개조식 항목 <ul class="bullet-list"> 래핑
- 페이지 분량 제한 프롬프트 강화
- 헤더/푸터 다중행 placeholder 설명 추가
"""

import json
import re
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from handlers.template.template_manager import TemplateManager
from pathlib import Path
from handlers.common import call_claude, extract_html       


# ★ 한글 포함 placeholder 정규식 (영문 + 숫자 + 언더스코어 + 한글)
PH_PATTERN = re.compile(r'\{\{([A-Za-z0-9_\uAC00-\uD7AF]+)\}\}')


class CustomDocTypeProcessor:
    """사용자 정의 문서 유형 처리기 (템플릿 기반)"""
    
    def __init__(self):
        self.doc_types_user = Path('templates/user/doc_types')
        self.template_manager = TemplateManager()
    
    def load_config(self, doc_type_id: str) -> dict:
        """config.json 로드"""
        config_path = self.doc_types_user / doc_type_id / 'config.json'
        if not config_path.exists():
            raise FileNotFoundError(f"문서 유형을 찾을 수 없습니다: {doc_type_id}")
        
        with open(config_path, 'r', encoding='utf-8') as f:
            return json.load(f)

    def load_content_prompt(self, doc_type_id: str, template_id: str = None) -> dict:
        """content_prompt.json 로드 (doc_type 우선 → template fallback)"""
        # ① doc_type 폴더
        path = self.doc_types_user / doc_type_id / 'content_prompt.json'
        if path.exists():
            with open(path, 'r', encoding='utf-8') as f:
                return json.load(f)
        
        # ② template 폴더 fallback
        if template_id:
            tpl_path = Path('templates/user/templates') / template_id / 'content_prompt.json'
            if tpl_path.exists():
                with open(tpl_path, 'r', encoding='utf-8') as f:
                    return json.load(f)
        
        return {}

    def load_template(self, doc_type_id: str) -> str:
        """template.html 로드 — template_manager 경유 (분리 구조)"""
        # ① config에서 template_id 확인
        config = self.load_config(doc_type_id)
        tpl_id = config.get("template_id")
        
        if tpl_id:
            # ★ 새 구조: template_manager에서 로드
            tpl_data = self.template_manager.load_template(tpl_id)
            if "html" in tpl_data:
                return tpl_data["html"]
        
        # ★ 하위 호환: 레거시 방식 (같은 폴더의 template.html)
        template_path = self.doc_types_user / doc_type_id / 'template.html'
        if template_path.exists():
            with open(template_path, 'r', encoding='utf-8') as f:
                return f.read()
        
        return None
    
    def generate(self, content: str, doc_type_id: str, options: dict = None,
                 image_data: dict = None) -> dict:
        """문서 생성 - 템플릿 + 사용자 입력

        Args:
            content: 사용자 입력 텍스트
            doc_type_id: 문서 유형 ID
            options: 추가 옵션 (instruction 등)
            image_data: 이미지 dict {binaryItemIDRef: {"base64": ..., "mime": ...}}
                        None이면 템플릿 폴더에서 자동 로드 시도
        """
        try:
            config = self.load_config(doc_type_id)
            template = self.load_template(doc_type_id)
            
            if template:
                # 이미지 데이터 준비
                if image_data is None:
                    image_data = self._load_image_data(config)
                result = self._generate_with_template(
                    content, config, template, options, image_data
                )
            else:
                result = self._generate_with_guide(content, config, options)
            
            return result
            
        except Exception as e:
            import traceback
            return {'error': str(e), 'trace': traceback.format_exc()}
    
    def _generate_with_template(self, content: str, config: dict, 
                                template: str, options: dict,
                                image_data: dict = None) -> dict:
        """템플릿 기반 생성 — content_prompt.json 활용"""
        
        context = config.get('context', {})
        structure = config.get('structure', {})
        instruction = options.get('instruction', '') if options else ''
        
        # ★ content_prompt 로드
        doc_type_id = config.get('id', '')
        template_id = config.get('template_id', '')
        cp = self.load_content_prompt(doc_type_id, template_id)
        
        placeholders_info = cp.get('placeholders', {})
        table_guide = cp.get('table_guide', {})
        writing_guide = cp.get('writing_guide', {})
        doc_info = cp.get('document', {})
        
        # ★ placeholder 가이드 생성 (type/pattern/example 포함)
        ph_guide_lines = []
        for ph_key, ph_info in placeholders_info.items():
            ph_type = ph_info.get('type', 'text')
            pattern = ph_info.get('pattern', '')
            example = ph_info.get('example', '')
            location = ph_info.get('location', '')
            
            line = f"  {ph_key}:"
            line += f"\n    type: {ph_type}"
            line += f"\n    pattern: {pattern}"
            if example:
                line += f"\n    example: \"{example}\""
            line += f"\n    location: {location}"
            ph_guide_lines.append(line)
        
        ph_guide = "\n".join(ph_guide_lines) if ph_guide_lines else "(no guide available)"
        
        # ★ 표 가이드 생성
        tbl_guide_lines = []
        for tbl_num, tbl_info in table_guide.items():
            headers = tbl_info.get('col_headers', [])
            col_types = tbl_info.get('col_types', [])
            merge = tbl_info.get('merge_pattern', {})
            bullets = tbl_info.get('bullet_chars', [])
            examples = tbl_info.get('example_rows', [])
            
            tbl_guide_lines.append(f"\n### Table {tbl_num}:")
            tbl_guide_lines.append(f"  Columns: {json.dumps(headers, ensure_ascii=False)}")
            if col_types:
                for ct in col_types:
                    tbl_guide_lines.append(
                        f"  Col {ct['col']} '{ct['header']}': {ct['type']}")
            if merge:
                tbl_guide_lines.append(f"  Merge: {json.dumps(merge, ensure_ascii=False)}")
                tbl_guide_lines.append(
                    f"  → row_group means: use rowspan to group rows by that column")
            if bullets:
                tbl_guide_lines.append(f"  Bullet chars: {bullets}")

            # ★ row_bf_pattern 추가
            bf_pattern = tbl_info.get('row_bf_pattern', [])
            if bf_pattern:
                tbl_guide_lines.append(f"  Row cell classes (apply to each <td>):")
                for bp in bf_pattern:
                    col = bp.get('col', '?')
                    bf_cls = bp.get('bf_class', '')
                    cs = bp.get('colSpan', 1)
                    rs = bp.get('rowSpan', 1)
                    span_info = ""
                    if cs > 1: span_info += f" colSpan={cs}"
                    if rs > 1: span_info += f" rowSpan={rs}"
                    tbl_guide_lines.append(
                        f'    col_{col}: class="{bf_cls}"{span_info}')

            if examples:
                tbl_guide_lines.append(f"  Example rows:")
                for ex in examples[:2]:
                    tbl_guide_lines.append(
                        f"    {json.dumps(ex, ensure_ascii=False)}")
        
        tbl_guide = "\n".join(tbl_guide_lines) if tbl_guide_lines else "No table guide"
        
        # ★ 페이지 추정
        page_estimate = structure.get('pageEstimate', 1)
        
        # ★ placeholder 키 목록 (from template)
        placeholders = PH_PATTERN.findall(template)
        placeholders = list(dict.fromkeys(placeholders))
        
        prompt = f"""Fill the template placeholders with reorganized content.

## Document Definition
{context.get('documentDefinition', 'structured document')}

## Context
- Type: {context.get('documentType', '')}
- Purpose: {context.get('purpose', '')}
- Audience: {context.get('audience', '')}
- Tone: {context.get('tone', '')}
- Layout: {doc_info.get('layout', 'portrait')}
- Page limit: {page_estimate} page(s). Be CONCISE.

## Writing Style
- Bullet chars: {writing_guide.get('bullet_styles', ['- ', '· '])}
- Primary font: {writing_guide.get('font_primary', '')}
- Keep lines ~{writing_guide.get('avg_line_length', 25)} chars average

## Placeholder Guide (type, pattern, example for each)
{ph_guide}

## Table Structure Guide
{tbl_guide}

## Input Content
{content[:6000] if content else '(empty)'}

## Additional Instructions
{instruction if instruction else 'None'}

## ALL Placeholders to fill (JSON keys):
{json.dumps(placeholders, ensure_ascii=False)}

## ★ Critical Rules
1. Output ONLY valid JSON — every placeholder above as a key
2. HEADER/FOOTER: use the PATTERN and modify the EXAMPLE for new content
   - department → user's department or keep example
   - author → user's name or keep example  
   - date → today's date in same format
   - slogan → keep exactly as example
3. TITLE: create title matching doc_title pattern from input content
4. TABLE_*_H_*: plain text column headers (use col_headers from guide)
5. TABLE_*_BODY: HTML <tr> rows only (no <table> wrapper)
   - Follow merge_pattern: row_group → use rowspan
   - Use bullet_chars from guide inside cells
   - Match example_rows structure
5b. TABLE_*_BODY <td>: apply class from 'Row cell classes' guide\n
   - e.g. <td class=\"bf-12\">content</td>\n
6. SECTION_*_CONTENT: use bullet style from writing guide
7. Empty string "" for inapplicable placeholders
8. Do NOT invent content — reorganize input only
9. PARA_*: reorganize input text for each paragraph placeholder
   - Keep the meaning, improve clarity and structure
   - PARA_n_RUN_m: if a paragraph has multiple runs, fill each run separately
10. IMAGE_*: output exactly "KEEP_ORIGINAL" (image is auto-inserted from source)
11. IMAGE_*_CAPTION: write a concise caption describing the image context
12. Total volume: {page_estimate} page(s)

Output ONLY valid JSON:"""

        try:
            response = call_claude(
                "You fill document template placeholders with reorganized content. "
                "Output valid JSON only. Respect the template structure exactly.",
                prompt,
                max_tokens=6000
            )
            
            fill_data = self._extract_json(response)
            
            if not fill_data:
                return {'error': 'JSON extraction failed', 'raw': response[:500]}
            
            html = self._fill_template(template, fill_data, image_data)
            
            return {'success': True, 'html': html}
            
        except Exception as e:
            import traceback
            return {'error': str(e), 'trace': traceback.format_exc()}
    
    def _fill_template(self, template: str, data: dict,
                       image_data: dict = None) -> str:
        """템플릿에 데이터 채우기

        Args:
            template: HTML 템플릿
            data: AI가 채운 placeholder → value dict
            image_data: 이미지 dict {binaryItemIDRef: {"base64": ..., "mime": ...}}
        """
        html = template

        # ★ content_prompt에서 IMAGE_n → binaryItemIDRef 매핑 빌드
        image_ref_map = self._build_image_ref_map(data, image_data)

        for key, value in data.items():
            placeholder = '{{' + key + '}}'

            # ── IMAGE_n: 원본 이미지 삽입 ──
            if re.match(r'^IMAGE_\d+$', key):
                img_tag = image_ref_map.get(key, '')
                html = html.replace(placeholder, img_tag)
                continue

            if isinstance(value, str) and value.strip():
                # ★ 개조식 내용 처리: · 또는 - 로 시작하는 항목
                lines = value.strip().split('\n')
                is_bullet_list = sum(
                    1 for l in lines
                    if l.strip().startswith('·') or l.strip().startswith('-')
                ) > len(lines) * 0.5

                if is_bullet_list and len(lines) > 1:
                    # ★ v2.2: inline context (<p><span> 안)에서는 <ul> 금지
                    # PARA_*, SECTION_*_TITLE, HEADER_*, FOOTER_*, TITLE_*, *_RUN_*
                    # 이들은 <p> 또는 <td> 안에 있어 block 요소 삽입 시 HTML 깨짐
                    _is_inline = re.match(
                        r'^(PARA_|SECTION_\d+_TITLE|HEADER_|FOOTER_|TITLE_|.*_RUN_)',
                        key
                    )
                    if _is_inline:
                        # <br> 줄바꿈으로 구조 보존
                        clean_lines = []
                        for item in lines:
                            item = item.strip()
                            if item.startswith('·'):
                                item = item[1:].strip()
                            elif item.startswith('-'):
                                item = item[1:].strip()
                            if item:
                                clean_lines.append(f'· {item}')
                        value = '<br>\n'.join(clean_lines)
                    else:
                        # <div> 안 (SECTION_*_CONTENT 등) → <ul><li> 허용
                        li_items = []
                        for item in lines:
                            item = item.strip()
                            if item.startswith('·'):
                                item = item[1:].strip()
                            elif item.startswith('-'):
                                item = item[1:].strip()
                            if item:
                                li_items.append(f'<li>{item}</li>')
                        value = '<ul class="bullet-list">\n' + '\n'.join(li_items) + '\n</ul>'

            html = html.replace(placeholder, str(value) if value else '')

        # ★ 남은 placeholder 정리 (한글 포함)
        html = PH_PATTERN.sub('', html)

        return html

    def _build_image_ref_map(self, data: dict, image_data: dict = None) -> dict:
        """IMAGE_n placeholder → <img> 태그 매핑 생성.

        content_prompt.json의 placeholders에서 IMAGE_n의 example_ref
        (= binaryItemIDRef)를 찾고, image_data에서 base64를 가져옴.
        """
        ref_map = {}
        if not image_data:
            return ref_map

        # content_prompt placeholders에서 IMAGE_n → ref 매핑
        # (generate 호출 시 content_prompt를 아직 안 가지고 있으므로
        #  template HTML의 data-ref 속성 또는 순서 매칭으로 해결)
        # 방법: template에서 IMAGE_1, IMAGE_2... 순서와
        #       image_data의 키 순서를 매칭

        # image_data 키 목록 (BinData 등장 순서)
        img_refs = sorted(image_data.keys())

        img_num = 0
        for ref in img_refs:
            img_num += 1
            key = f"IMAGE_{img_num}"
            img_info = image_data[ref]

            b64 = img_info.get("base64", "")
            mime = img_info.get("mime", "image/png")

            if b64:
                ref_map[key] = (
                    f'<img src="data:{mime};base64,{b64}" '
                    f'alt="{ref}" style="max-width:100%; height:auto;">'
                )
            else:
                # base64 없으면 파일 경로 참조
                file_path = img_info.get("path", "")
                if file_path:
                    ref_map[key] = (
                        f'<img src="{file_path}" '
                        f'alt="{ref}" style="max-width:100%; height:auto;">'
                    )
                else:
                    ref_map[key] = f'<!-- image not found: {ref} -->'

        return ref_map

    def _load_image_data(self, config: dict) -> dict:
        """템플릿 폴더에서 images.json 로드 (BinData 추출 결과).

        images.json 구조:
        {
            "IMG001": {"base64": "iVBOR...", "mime": "image/png"},
            "IMG002": {"base64": "...", "mime": "image/jpeg"}
        }

        또는 이미지 파일이 직접 저장된 경우 경로를 반환.
        """
        tpl_id = config.get("template_id", "")
        if not tpl_id:
            return {}

        tpl_path = Path('templates/user/templates') / tpl_id

        # ① images.json (base64 저장 방식)
        images_json = tpl_path / 'images.json'
        if images_json.exists():
            try:
                with open(images_json, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except:
                pass

        # ② images/ 폴더 (파일 저장 방식)
        images_dir = tpl_path / 'images'
        if images_dir.exists():
            result = {}
            mime_map = {
                '.png': 'image/png', '.jpg': 'image/jpeg',
                '.jpeg': 'image/jpeg', '.gif': 'image/gif',
                '.bmp': 'image/bmp', '.svg': 'image/svg+xml',
                '.wmf': 'image/x-wmf', '.emf': 'image/x-emf',
            }
            for img_file in sorted(images_dir.iterdir()):
                if img_file.suffix.lower() in mime_map:
                    ref = img_file.stem  # 파일명 = binaryItemIDRef
                    result[ref] = {
                        "path": str(img_file),
                        "mime": mime_map.get(img_file.suffix.lower(), "image/png")
                    }
            return result

        return {}
    
    def _extract_json(self, response: str) -> dict:
        """응답에서 JSON 추출"""
        # ```json ... ``` 블록 찾기
        match = re.search(r'```json\s*(.*?)\s*```', response, re.DOTALL)
        if match:
            try:
                return json.loads(match.group(1))
            except:
                pass
        
        # 가장 큰 { } 블록 찾기
        brace_depth = 0
        start = -1
        for i, ch in enumerate(response):
            if ch == '{':
                if brace_depth == 0:
                    start = i
                brace_depth += 1
            elif ch == '}':
                brace_depth -= 1
                if brace_depth == 0 and start >= 0:
                    try:
                        return json.loads(response[start:i+1])
                    except:
                        start = -1
        
        return None
    
    def _generate_with_guide(self, content: str, config: dict, options: dict) -> dict:
        """가이드 기반 생성 (템플릿 없을 때)"""
        
        context = config.get('context', {})
        structure = config.get('structure', {})
        layout = config.get('layout', {})
        style = config.get('style', {})
        
        instruction = options.get('instruction', '') if options else ''
        
        # 섹션 구조 설명
        sections = layout.get('sections', [])
        sections_desc = ""
        for i, sec in enumerate(sections, 1):
            sections_desc += f"""
{i}. {sec.get('name', f'섹션{i}')}
   - 작성 스타일: {sec.get('writingStyle', '혼합')}
   - 불릿: {'있음' if sec.get('hasBulletIcon') else '없음'}
   - 표: {'있음' if sec.get('hasTable') else '없음'}
   - 내용: {sec.get('contentDescription', '')}
"""
        
        page_estimate = structure.get('pageEstimate', 1)
        
        system_prompt = f"""당신은 "{context.get('documentType', '문서')}" 작성 전문가입니다.

## 문서 특성
- 목적: {context.get('purpose', '')}
- 대상: {context.get('audience', '')}
- 톤: {context.get('tone', '')}
- 전체 스타일: {structure.get('writingStyle', '혼합')}
- 분량: 약 {page_estimate}페이지

## 문서 구조
{sections_desc}

## 작성 원칙
{chr(10).join('- ' + p for p in structure.get('writingPrinciples', []))}

## 주의사항
{chr(10).join('- ' + m for m in structure.get('commonMistakes', []))}

## 핵심!
- 사용자 입력을 **정리/재구성**하세요
- **새로 창작하지 마세요**
- 분석된 문서 구조를 그대로 따르세요
- 개조식 섹션은 "· " 불릿 사용
- 분량을 {page_estimate}페이지 내로 제한하세요"""

        user_prompt = f"""다음 내용을 "{context.get('documentType', '문서')}" 양식으로 정리해주세요.

## 입력 내용
{content[:6000] if content else '(내용 없음)'}

## 추가 요청
{instruction if instruction else '없음'}

## 출력 형식
완전한 A4 규격 HTML 문서로 출력하세요.
- <!DOCTYPE html>로 시작
- UTF-8 인코딩
- @page {{ size: A4 }} CSS 포함
- 폰트: {style.get('font', {}).get('name', '맑은 고딕')}
- 머릿말/꼬리말 포함
- 약 {page_estimate}페이지 분량

HTML만 출력하세요."""

        try:
            response = call_claude(system_prompt, user_prompt, max_tokens=6000)
            html = extract_html(response)
            
            if not html:
                return {'error': 'HTML 생성 실패'}
            
            return {'success': True, 'html': html}
            
        except Exception as e:
            import traceback
            return {'error': str(e), 'trace': traceback.format_exc()}