#!/usr/bin/env python3
"""
听力拖拽 (core_listening_drag) 多步 LLM prompt 模板

生成流水线：
  Step 1: 学习过程配置 (intermediate text)
  Step 2: 互动描述 (intermediate text)
  Step 3: taskData → JSON
  Step 4: preDialog → JSON
  Step 5: dialogList → JSON
  Step 6: questionList → JSON
  Step 7: learningData → JSON
"""

# ── Step 1: 学习过程配置 ──
STEP1_LEARNING_PROCESS = r"""你是一个少儿英语学习的出题专家。请根据以下任务信息，生成一个结构化的听力学习引导框架，帮助孩子逐步完成一篇听力中重点句子的学习。

# 你的任务
根据{听力材料}理解当前上下文，结合{关联知识}中的内容， 针对{学习流程}中的句子生成一个 markdown 格式的教学流程。
这个任务的目标是引导孩子 1). 准确匹配耳中听到的句子和眼前看到的文本（完整匹配或仅匹配词汇/短语/句型），2). 准确匹配英文文本和中文意思，从而准确理解一个听力句子的意思。学习流程中，Brainy 将逐轮引导孩子仔细听音频句子、选择正确匹配的句子英文文本、选择正确的中文意思。

每一轮重点句子的学习包含以下内容：
1.一段对重点句子结尾【xxx】标记的重点内容(如未标记，则自行选择一段重点内容)的讲解文本。注意，重点内容要以 原型 单词/短语 来讲解， 不要增加 单复数、时态等变化。

2. 听音选句
   - Brainy引导出题: 描述题干
   - 选择题包含 2 个英文选项（1个正确选项，内容为当前重点句子，在其后标识出"[正确]" ， 1个干扰项。正确选项与干扰项的差异，需要围绕重点句子末尾用【xxx】标识的重点内容来设计；选项顺序随机）。
   - 每个选项配有 Brainy 的中文反馈：
     - 正确选项：语气积极的正反馈。
     - 错误选项：自然指出错误原因，避免批评，并鼓励再尝试。

3. 听音选意
   - Brainy 播放重点句子的音频（显示对应文本），提出一个选择题。
   - 选择题题目设置为 「这句话的中文意思」，然后使用自然流畅的过渡词问用户说的这个意思对不对。
   - 选择题包含2个选项：对 / Yes   错 / No
   - 每个选项配有 Brainy 的中文反馈：
     - 正确选项：再进一步解析一下正确答案中的重点词汇/短语/句型（非必须），夸奖用户掌握了句子意思。
     - 错误选项：简单说明哪里不对（避免出现正确选项里的信息），并鼓励再尝试。
   - 正确选项不要都集中在"对 / Yes"上面，需要有对应的正确选项为"错 / No"。

全部重点句子都学完后，请附上一个任务完成反馈，用自然语言总结：用户已经完成全部题目，掌握了这篇听力中的所有重点句子。
注意，整个配置为一套线性的学习流程。已经考察过的题目及干扰项，后面需要变换，不要重复出现。

# 输出格式（请严格遵循）
```
# 重点句子 1
音频文本：{重点句子文本内容}
讲解文本：{对重点句子的重点内容的讲解文本}
1. 听音选句
Brainy：{题干，询问那个选项与播放的重点句子音频相对应}
- {选项 A}
  - Brainy：{选项 A 的反馈}
- {选项 B}
  - Brainy：{选项 B 的反馈}

2. 听音选意
Brainy：{题干，询问该句英文对应的正确中文意思}
- {选项 A}
  - Brainy：{选项 A 的反馈}
- {选项 B}
  - Brainy：{选项 B 的反馈}
---
# 重点句子 2
（同上结构）
---
# 重点句子 N
（同上结构）
---
# 完成反馈
Brainy：很好！你已经回答完了全部问题，相信你已经听懂了这个任务中所有的重点句子。现在让我们来查看结果吧!
```

# 注意事项
- 所有英文选项应为 CEFR A1/A2 水平，句子简短自然。英文使用英式拼写。
- Brainy 的反馈语气要亲切、鼓励、富有引导感。
- 正确选项位置随机，不总放第一个。
- 输出格式严格使用 markdown，无需附加其他解释或输入信息。

# 输出样例
以下是一些完整的输出样例。

样例1
```
# 重点句子 1
音频文本： We have entered a storm
讲解文本：刚刚听到的音频中，包含了一个关键单词 storm，意思为暴风雨。正确的发音是 storm。
1. 听音选句
Brainy：接下来，我们来试试，你能不能听出以下哪一句话中，包含了关键词 storm。
- We have entered a storm. [正确]
  Brainy：太棒了！你准确听出了句子中的重点单词 storm！
- We have entered a forest.
  Brainy：Oops，这句话说到了 forest，并没有 storm 哦。再仔细听一听正确的选项吧！
2. 听音选意
Brainy："我们进入了一个森林"这句话的意思是这个吗？
- 对 / Yes
  Brainy：这句话中，storm 是 "风暴"的意思，再仔细看看句子内容吧。
- 错 / No [正确]
  Brainy：没错！你不仅听懂了这句话，还掌握了 enter a storm 这个短语！
# 重点句子 2
音频文本： The ship is broken.
讲解文本：刚刚听到的音频中，包含了一个关键单词 broken，意思为破损的、坏的。正确的发音是 broken。
1. 听音选句
Brainy：接下来，我们来试试，你能不能听出以下哪一句话中，包含了关键词 broken。
- The ship is repaired.
  Brainy：这句话里没有出现 broken，出现了 repaired。再仔细听听正确答案里的音频内容吧！
- The ship is broken. [正确]
  Brainy：没错！你准确听出了句子里包含的关键词 broken。

2. 听音选意
Brainy："这艘船坏了"这句话的意思是这个吗？
- 对 / Yes [正确]
  Brainy：Bingo！你不仅理解了句子意思，还掌握了  broken 这个词汇！
- 错 / No
  Brainy：句子里说的是 broken（坏了），而不是  repaired（修好了）哦，再选一次试试看～

# 完成反馈
Brainy：很好！你已经回答完了全部问题，相信你已经听懂了这个任务中所有的重点句子。现在让我们来查看结果吧!
```



# 输入
{听力材料}:
$听力材料$

{学习流程}:
$学习流程$

{专有词汇}:
$专有词汇$

{关联知识}：
$关联知识$


# 输出
如果{听力材料}和{学习流程句子}包含有效文本，则按上述要求输出配置内容。
如果{听力材料}或者{学习流程句子}内容为空，则直接输出"无"。"""


# ── Step 2: 互动描述 ──
STEP2_INTERACTION_DESC = r"""# 任务目标
你是一个儿童英语学习内容生成助手。现在有一个听力理解互动配置，需要将其转写为对话记录中的叙事化描述，用于回顾剧情。

# 背景知识
- 听力理解是剧情中的核心互动，要求孩子聆听一份或多份音频材料（如录音、语音留言、对话片段等），并回答多个问题才能通过。
- 互动内容（即孩子听完材料并作答的过程）需要转写为自然的叙事描述。

# 输入信息
- 任务名称：$任务名称$
- 题目材料：$题目材料$

# 输出要求
1. 生成一条自然叙事描述。
2. 描述以英文为主，中文放在括号中，必须在同一行里。
3. 描述长度约 40–60 英文词，适合总结该材料的关键信息。
4. 描述必须包含对应材料中的核心信息，并结合正确答案来组织内容。
5. 描述要自然，像剧情推进，而不是答题总结。
6. 避免教学感，不能出现"问题""选项""正确答案"等字眼。
7. 输出时只返回最终的描述文本，不要解释，不要使用额外的样式。

# 输出示例
输出：
You listened to Barry's energetic rap and heard him boast about Speedy Monkey's harbour being the world's top with the fastest icebreaker. (你听了 Barry 充满活力的说唱，听到他夸耀 Speedy Monkey 的码头是世界顶尖的，拥有最快的破冰船。)"""


# ── Step 3: taskData ──
STEP3_TASK_DATA = r"""# 字段内容要求
{
    "cType": "core_listening_drag",
    "cId": {ID},
    "title": {用户任务配置}中的【任务标题】,
    "sceneDesc": {用户任务配置}中的【任务背景】,
    "key"(基于{用户任务知识}抽取): [
        {
            "desc": "",
            "keyList": [
                {
                    "type": "default",
                    "content": 知识点内容,
                    "desc": 知识点解释
                },
                 ... ...
            ]
        },
        ...
    ],
    "resourceMapping"(抽取出{角色配置}中的全部 name:id映射 可为空。id必须为int类型):
    {
    样例:
    "Tom":43,
    "Jerry":10
    },
    "cDesc": "" // {互动描述} 中的内容，保持内容不变，注意内容换行在 json 的转译
}

# 输出格式
单行json输出。
样例:
{"cType":"core_listening_drag","cId":"0000009","title":"通过语音备忘录找到神秘人的线索。","sceneDesc":"在尘封的旧世界电脑中找到了神秘人留下的语音备忘录！看起来好像是说给自己的记录日志？不知道有没有留下关于去向的线索，和同行的伙伴们一起来听听看叭！","key":[{"desc":"","keyList":[{"type":"default","content":"Journey","desc":"n. 旅行"},{"type":"default","content":"Adventure","desc":"n. 冒险"}]}],"resourceMapping":{},"cDesc":"You listened to Barry's energetic rap and heard him boast about Speedy Monkey's harbour being the world's top with the fastest icebreaker. (你听了 Barry 充满活力的说唱，听到他夸耀 Speedy Monkey 的码头是世界顶尖的，拥有最快的破冰船。)  "}

# 输入
{ID}:
$ID$

{用户任务配置}:
$用户任务配置$

{用户任务知识}:
$用户任务知识$

{角色配置}:
$角色配置$

{互动描述}:
$互动描述$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***
如果{任务配置}中没有有效文本，则输出"无"。"""


# ── Step 4: preDialog ──
STEP4_PRE_DIALOG = r"""# 要求
将给定的{开场语}抽取为以下格式，注意如果{开场语}为无则为空，即[]:
[{
        "type": "npc",  // npc 或 user
        "npcName": "Brainy", // 如果type位user则没有该字段
        "content": "xxxxxx.",
}
......
]

# 输出格式
单行json输出。
样例1:
[{"type":"npc","npcName":"Brainy","content":"OK,let's try to the scene the scene together."}, {"type":"user","content":"OK,let's try to the scene the scene together."}]

样例2:
[{"type":"user","content":"OK,let's try to the scene the scene together."}]

样例3:
[]

# 输入
{开场语}:
$开场语$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***，直接以"["开始输出。
如果{材料内容}中没有有效文本，则输出"无"。"""


# ── Step 5: dialogList ──
STEP5_DIALOG_LIST = r"""# 要求
将给定的配置数据抽取 为以下格式:
[
        [ // 每个文本对应一个，根据对话内容抽取
            {
                "type": "npc",
                "npcName": "Brainy",
                "content": "What's the color of the pliers?",
                "trans":"", // 结合整段文本内容给出这句话的翻译
                "isPoint": false // 如果这句话后面有（重点句）表示则为true，否则为false
            },
            {
                "type": "user", // 如果为user则没有npcName
                "content": "What's the color of the pliers?",
                "trans":"",
                "isPoint": true
            },
            {
                "type": "npc",
                "npcName": "Brainy",
                "content": "What's the color of the pliers?",
                "trans":"",
                "isPoint": true
            }
        ],
        ......
]

# 输出格式
单行json输出。
样例:
[[{"type":"npc","npcName":"Alex","content":"Do you mind if I turn up the volume?","trans":"你介意我把音量调大一点吗？","isPoint":false},{"type":"npc","npcName":"Alex","content":"I can't hear the dialogue clearly.","trans":"我听不清对白。","isPoint":false},{"type":"npc","npcName":"Alex","content":"The neighbors are making too much noise outside.","trans":"邻居在外面太吵了。","isPoint":true},{"type":"npc","npcName":"User","content":"I'd rather you didn't.","trans":"我宁愿你别这么做。","isPoint":false},{"type":"npc","npcName":"User","content":"I'm trying to concentrate on my work.","trans":"我正想专心工作呢。","isPoint":false},{"type":"npc","npcName":"User","content":"Put on some headphones instead—they're right there on the table.","trans":"不如戴上耳机吧——就在桌子上。","isPoint":false},{"type":"npc","npcName":"Alex","content":"But I don't like wearing headphones!","trans":"可我不喜欢戴耳机！","isPoint":true},{"type":"npc","npcName":"User","content":"Well, then maybe watch something without talking later.","trans":"那好吧，待会儿看个没对白的节目好了。","isPoint":false}],[{"type":"npc","npcName":"Alex","content":"Do you mind if I open the window?","trans":"你介意我开窗吗？","isPoint":false},{"type":"npc","npcName":"Alex","content":"It's really stuffy in here.","trans":"这里实在太闷了。","isPoint":false},{"type":"npc","npcName":"Alex","content":"The air feels heavy after cooking.","trans":"做完饭后空气感觉很沉闷。","isPoint":true},{"type":"npc","npcName":"User","content":"I'd rather you didn't.","trans":"我宁愿你别开。","isPoint":false},{"type":"npc","npcName":"User","content":"I just lit a candle and I don't want the scent to blow away.","trans":"我刚点了一支香薰蜡烛，不想让香味散掉。","isPoint":false},{"type":"npc","npcName":"User","content":"You could use the fan—it's quieter and won't mess with the smell.","trans":"你可以用风扇——它更安静，也不会影响香味。","isPoint":false},{"type":"npc","npcName":"Alex","content":"But I prefer fresh air!","trans":"但我更喜欢新鲜空气！","isPoint":true},{"type":"npc","npcName":"User","content":"Fine, just open it a little—just for ten minutes.","trans":"好吧，那就开一点点——就十分钟。","isPoint":false}]]

# 输入
{听力材料}:
$听力材料$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***，直接以"["开始输出。
如果{材料内容}中没有有效文本，则输出"无"。"""


# ── Step 6: questionList ──
STEP6_QUESTION_LIST = r"""# 要求
将给定的配置数据抽取 为以下格式:
[{
        "imageInfo": {
                "questionImg": "{ID}_question.png",
                "answerImg": "{ID}_answer.png"
        },
        "optionList": [{ // 根据{题目}中的选项图片编号抽取
                        "option": "{ID}_option_00.png"
                },
                {
                        "option": "{ID}_option_01.png"
                },
                {
                        "option": "{ID}_option_02.png"
                },
                {
                        "option": "{ID}_option_03.png"
                }
                ......
        ],
        "answer": [2, 4, 1], // 根据{题目}中的答案图片编号抽取
        "answerType": "" // {题目}如果为#单空选择则内容为"all"，否则为空，即""
}]

# 输出格式
单行json输出。
样例:
[{"imageInfo":{"questionImg":"012310_question.png","answerImg":"012310_answer.png"},"optionList":[{"option":"012310_option_00.png"},{"option":"012310_option_01.png"},{"option":"012310_option_02.png"},{"option":"012310_option_03.png"}],"answer":[2,1],"answerType":""}]

# 输入
{ID}:
$ID$

{题目}:
$题目$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***
（如果{材料配置}中没有有效文本，则直接输出"无"。）"""


# ── Step 7: learningData ──
STEP7_LEARNING_DATA = r"""# 字段内容要求
基于{学习配置}内容整理成以下结构：
{
"learningPart"(每个重点句子对应的内容为一个item): [    {
        "type": "npc",
        "npcName": "Brainy", //默认 除非配置中单独提供了其他角色
        "content": "xxx", //音频文本
        "learning":[{
                "question":{
                    "content": "xxx",  // 讲解文本
                    "type": "npc",
                    "npcName": "Brainy" //默认 除非配置中单独提供了其他角色
                },
                "extendQuestion":{ // 从听音选句中Brainy说的话抽取
                    "content": "xxx",  // Brainy说的内容
                    "type": "npc",
                    "npcName": "Brainy" //默认 除非配置中单独提供了其他角色
               },
                "optionList": [{ // 从听音选句中抽取
                     "option": "xxx", // 选项内容
                     "feedback": {
                            "type": "npc",
                            "content": "xxx", //反馈
                            "npcName": "xxx" //一般为Brainy
            },
                       },
                       ... ...
                ],
                 "answer": [int]  // 正确选项对应的数组索引
                },
            {
                "question":{
                    "content": "xxx",  // 音频文本
                    "type": "npc",
                    "npcName": "Brainy" //默认 除非配置中单独提供了其他角色
                },
                "extendQuestion":{ // 从听音选意中Brainy说的话抽取
                    "content": "xxx",  // Brainy说的内容
                    "type": "npc",
                    "npcName": "Brainy" //默认 除非配置中单独提供了其他角色
               },
               "optionList": [{// 从听音选意中抽取
                     "option": "xxx", // 选项内容
                     "feedback": {
                            "type": "npc",
                            "content": "xxx", //反馈
                            "npcName": "xxx" //一般为Brainy
                    },
                       },
                       ... ...
             ]
            }
]
            },
                ... ... （句子2、句子3 ... 的解析内容）
       ],
"closing": {
                "type": "npc",
                "content": "xxx", //总结
                "npcName": "xxx" //一般为Brainy
            },
}
注意:  双引号字符串文本中如果出现英文双引号，需要使用转义符  确保最终输出是合法的json。
# 输出格式
单行json输出。
样例:
{"learningPart":[{"type":"npc","npcName":"Brainy","content":"需要生产音频的，引用的句子文本","learning":[{"question":{"type":"npc","npcName":"Brainy","content":"你刚刚听到的音频当中，包含了tallest单词，意思为最高的。正确的发音时tallest."},"extendQuestion":{"type":"npc","npcName":"Brainy","content":"接下来我要问你一个问题，请你如实作答。"},"optionList":[{"option":"I'm sorry, Mia.","feedback":{"type":"npc","npcName":"Brainy","content":"太棒了！"}},{"option":"I'm happy to tell you.","feedback":{"type":"npc","npcName":"Brainy","content":"再确认下正确答案吧。"}}],"answer":[0]},{"question":{"type":"npc","npcName":"Brainy","content":"How about there? Between the two tallest trees?"},"extendQuestion":{"type":"npc","npcName":"Brainy","content":"「那儿怎么样?在两棵最高的树之间?」这句话是这个意思吗?我说的是对还是错呢?"},"optionList":[{"option":"对","feedback":{"type":"npc","npcName":"Brainy","content":"太棒了！"}},{"option":"错","feedback":{"type":"npc","npcName":"Brainy","content":"再确认下正确答案吧。"}}],"answer":[0]}]}],"closing":{"type":"npc","npcName":"Brainy","content":"恭喜你！你已经成功地完成了这个听力任务。现在让我们来查看结果吧!"}}

# 输入
{学习配置}:
$学习配置$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！*** 以"{"开始输出。
（如果{学习配置}中没有有效文本，则直接输出"无"。）"""


# ── Steps registry ──
CORE_LISTENING_DRAG_STEPS = {
    "step1_learning_process": {
        "template": STEP1_LEARNING_PROCESS,
        "description": "学习过程配置",
        "output_type": "text",
    },
    "step2_interaction_desc": {
        "template": STEP2_INTERACTION_DESC,
        "description": "互动描述",
        "output_type": "text",
    },
    "step3_task_data": {
        "template": STEP3_TASK_DATA,
        "description": "taskData",
        "output_type": "json",
    },
    "step4_pre_dialog": {
        "template": STEP4_PRE_DIALOG,
        "description": "preDialog",
        "output_type": "json",
    },
    "step5_dialog_list": {
        "template": STEP5_DIALOG_LIST,
        "description": "dialogList",
        "output_type": "json",
    },
    "step6_question_list": {
        "template": STEP6_QUESTION_LIST,
        "description": "questionList",
        "output_type": "json",
    },
    "step7_learning_data": {
        "template": STEP7_LEARNING_DATA,
        "description": "learningData",
        "output_type": "json",
    },
}


def build_listening_drag_prompt(step_key, replacements):
    """Build a prompt for a given step by replacing $placeholder$ tokens."""
    info = CORE_LISTENING_DRAG_STEPS[step_key]
    prompt = info["template"]
    for key, val in replacements.items():
        prompt = prompt.replace(f"${key}$", str(val) if val else "")
    return prompt
