"""Prompt模板引擎 - Context Engineering风格的模块化Prompt系统

核心设计:
- PromptSection: 模块化section定义（identity/context/instructions/constraints/output_format/examples）
- PromptTemplate: 支持${variable}变量注入、token预算管理、渲染为OpenAI messages格式
"""

import re
from dataclasses import dataclass


@dataclass
class PromptSection:
    """Prompt模块化section

    每个section对应prompt中的一个语义模块，渲染时按规则组装为
    system/user两个message。
    """

    identity: str = ""  # Agent身份定义
    context: str = ""  # 动态上下文（知识库、品牌信息等）
    instructions: str = ""  # 任务指令
    constraints: str = ""  # 约束条件
    output_format: str = ""  # 输出格式要求
    examples: str = ""  # 少样本示例（可选）


class PromptTemplate:
    """Context Engineering风格的Prompt模板

    特性:
    - 支持 ${variable} 和 ${nested.path} 变量注入
    - 支持 token 预算管理（智能截断context section，避免Lost-in-the-Middle）
    - 渲染为 OpenAI messages 格式

    渲染规则:
    - system message = identity + context(截断到budget) + constraints
    - user message = instructions + output_format + examples
    """

    def __init__(self, sections: PromptSection):
        self.sections = sections

    def render(
        self, variables: dict | None = None, context_budget: int = 3000
    ) -> list[dict]:
        """渲染为messages列表

        Args:
            variables: 变量字典，支持嵌套路径如 {"brand": {"name": "xxx"}}
            context_budget: context section的token预算上限

        Returns:
            [{"role": "system", "content": "..."}, {"role": "user", "content": "..."}]
        """
        variables = variables or {}

        # 1. 注入变量到所有section
        identity = self._inject(self.sections.identity, variables)
        context = self._inject(self.sections.context, variables)
        instructions = self._inject(self.sections.instructions, variables)
        constraints = self._inject(self.sections.constraints, variables)
        output_format = self._inject(self.sections.output_format, variables)
        examples = self._inject(self.sections.examples, variables)

        # 2. 截断context到token预算
        context = self._truncate_smart(context, context_budget)

        # 3. 组装messages
        system_parts = [identity, context, constraints]
        system_content = "\n\n".join(p for p in system_parts if p.strip())

        user_parts = [instructions, output_format, examples]
        user_content = "\n\n".join(p for p in user_parts if p.strip())

        messages = []
        if system_content:
            messages.append({"role": "system", "content": system_content})
        if user_content:
            messages.append({"role": "user", "content": user_content})

        return messages

    def _inject(self, text: str, variables: dict) -> str:
        """替换 ${var} 和 ${var.nested.path} 占位符

        支持多层嵌套字典路径解析，如 ${brand.name} 会从
        {"brand": {"name": "xxx"}} 中取出 "xxx"。
        未匹配的变量保持原样。
        """
        if not text:
            return text

        def replacer(match):
            key = match.group(1)
            parts = key.split(".")
            value = variables
            for part in parts:
                if isinstance(value, dict):
                    value = value.get(part, f"${{{key}}}")
                else:
                    return f"${{{key}}}"
            return str(value) if not isinstance(value, dict) else f"${{{key}}}"

        return re.sub(r"\$\{([^}]+)\}", replacer, text)

    def _truncate_smart(self, text: str, max_tokens: int) -> str:
        """智能截断：保留开头和结尾，中间省略

        策略：保留开头50%和结尾30%的字符预算，中间部分省略。
        这种策略基于Lost-in-the-Middle研究，LLM对首尾信息的
        注意力更强，中间部分容易被忽略。
        """
        estimated_tokens = self._estimate_tokens(text)
        if estimated_tokens <= max_tokens:
            return text

        if not text:
            return text

        # 按字符近似截断（1 token ≈ 1.5 chars for中英混合）
        char_budget = int(max_tokens * 1.5)
        head_budget = int(char_budget * 0.5)
        tail_budget = int(char_budget * 0.3)

        head = text[:head_budget]
        tail = text[-tail_budget:]
        return f"{head}\n\n[... 中间内容已省略以控制上下文长度 ...]\n\n{tail}"

    def _estimate_tokens(self, text: str) -> int:
        """粗略估算token数

        中文字符约1 token/字，英文约4字符/token。
        这是一个粗略估计，实际tokenizer会更精确，
        但对于预算管理来说足够使用。
        """
        if not text:
            return 0
        chinese_chars = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
        other_chars = len(text) - chinese_chars
        return chinese_chars + int(other_chars / 4)
