from __future__ import annotations

from typing import Any


BIAS_DETECTION_INSTRUCTIONS = (
    "## 偏误检测要求（必须逐项检查）"
    "\n- **确认偏误**：是否只引用支持己方的数据？是否忽略了不利证据？"
    "\n- **樱桃采摘**：是否从大量数据中只挑选了有利的少数案例？"
    "\n- **过度自信**：置信度是否与实际证据强度匹配？是否存在无根据的确定性断言？"
    "\n- **信息茧房**：是否只从单一来源或单一视角获取信息？"
    "\n- **锚定效应**：是否过度依赖初始信息而忽略了后续更新？"
    "\n- **群体思维**：是否因为多数人意见而放弃了独立判断？"
)

BLIND_SPOT_DETECTION_INSTRUCTIONS = (
    "## 盲区检测要求"
    "\n- **时间盲区**：短期影响和长期影响是否都被考虑？"
    "\n- **地域盲区**：局部影响和全局影响是否都被分析？"
    "\n- **利益相关方盲区**：是否有重要的利益相关方未被提及？"
    "\n- **连锁反应盲区**：二阶、三阶效应是否被考虑？"
    "\n- **黑天鹅盲区**：低概率高影响事件是否被提及？"
)

RELIABILITY_SCORING_INSTRUCTIONS = (
    "## 可靠性评分要求"
    "\n对本方和对方的每个论点进行可靠性评分（1-5分）："
    "\n- 5分：多源强证据支持，逻辑严密，可复现"
    "\n- 4分：有可靠证据支持，逻辑基本完整"
    "\n- 3分：有一定证据但不够充分，或逻辑有小瑕疵"
    "\n- 2分：证据薄弱，主要基于推测"
    "\n- 1分：纯推测或明显错误"
    "\n"
    "\n同时标注：偏误标记（如有）、证据强度（strong/moderate/weak/speculative）"
)


def debate_role_instruction(role: str) -> str:
    _HILL_CLIMBING = (
        "\n\n【迭代攀升要求】当你收到前序轮次的质疑或挑战时，你必须："
        "（1）逐条审视自己先前的论点，识别被有效挑战的部分，用「✅保留 / ❌放弃 / 🔄修正」标注每条论点的状态；"
        "（2）保留有充分证据支撑的核心主张，修正或放弃被证伪的子论点；"
        "（3）补充新的证据或推理来强化薄弱环节——至少提供1条新的支持性证据来回应质疑；"
        "（4）明确标注置信度的变化及原因，格式如'置信度从0.7→0.5，原因：被质疑的供应链数据缺乏独立验证'；"
        "（5）如果挑战者指出了关键盲区，你必须在修订中正面回应，不得回避或重复初始论点。"
        "你的目标不是固守初始立场，而是在每一轮交互中让分析更加精确和可靠。"
    )
    _EVIDENCE_CITATION = (
        "\n\n【证据引用规范】每个论点必须附带具体来源：引用具体的报告名称、发布机构、日期、"
        "数据指标（如GDP增长率、军费开支、部署数量等）、条约编号或历史先例。"
        "引用格式要求：[来源类型:报告名称/数据库名, 发布机构, 日期]。"
        "可接受的来源类型包括：SIPRI军费数据、世界银行/IMF经济指标、兰德公司/CSIS等智库报告、"
        "联合国决议文本、政府白皮书、已验证的新闻报道（注明媒体名称和日期）。"
        "禁止使用'据相关报道''一般来说''众所周知'等模糊表述。"
        "每条论点至少引用1个可验证来源；如果缺乏直接证据，必须明确标注为'推测性分析'并给出推理链条。"
    )
    _BIAS_DETECTION = (
        "\n\n## 偏误检测要求（必须逐项检查）"
        "\n- **确认偏误**：是否只引用支持己方的数据？是否忽略了不利证据？"
        "\n- **樱桃采摘**：是否从大量数据中只挑选了有利的少数案例？"
        "\n- **过度自信**：置信度是否与实际证据强度匹配？是否存在无根据的确定性断言？"
        "\n- **信息茧房**：是否只从单一来源或单一视角获取信息？"
        "\n- **锚定效应**：是否过度依赖初始信息而忽略了后续更新？"
        "\n- **群体思维**：是否因为多数人意见而放弃了独立判断？"
    )
    _BLIND_SPOT_DETECTION = (
        "\n\n## 盲区检测要求"
        "\n- **时间盲区**：短期影响和长期影响是否都被考虑？"
        "\n- **地域盲区**：局部影响和全局影响是否都被分析？"
        "\n- **利益相关方盲区**：是否有重要的利益相关方未被提及？"
        "\n- **连锁反应盲区**：二阶、三阶效应是否被考虑？"
        "\n- **黑天鹅盲区**：低概率高影响事件是否被提及？"
    )
    _RELIABILITY_SCORING = (
        "\n\n## 可靠性评分要求"
        "\n对本方和对方的每个论点进行可靠性评分（1-5分）："
        "\n- 5分：多源强证据支持，逻辑严密，可复现"
        "\n- 4分：有可靠证据支持，逻辑基本完整"
        "\n- 3分：有一定证据但不够充分，或逻辑有小瑕疵"
        "\n- 2分：证据薄弱，主要基于推测"
        "\n- 1分：纯推测或明显错误"
        "\n"
        "\n同时标注：偏误标记（如有）、证据强度（strong/moderate/weak/speculative）"
    )
    _CROSS_DOMAIN = (
        "\n\n【跨域关联要求】你的分析不应局限于本专业领域。必须主动识别并引用至少1个其他维度"
        "的关联因素。跨域关联的具体模式包括："
        "经济→地缘：制裁政策如何改变贸易路线和联盟经济依赖；"
        "军事→经济：军备竞赛对财政赤字和民用投资的挤出效应；"
        "技术→社会：AI自动化对就业结构和政治稳定的冲击；"
        "地缘→军事：地理通道变化对力量投射的约束；"
        "社会→政治：民意变化对决策窗口期的影响。"
        "在论证中明确指出跨域因果链，格式如'经济制裁→供应链中断→军事后勤压力→作战计划调整'。"
        "如果其他专家的分析涉及你的领域，在修订轮中必须提供专业判断。"
    )

    prompts = {
        # ── 核心辩论角色 ──────────────────────────────────────
        "advocate": (
            "你是【战略支持者🟢】，负责为命题构建系统性的支持论证。"
            "你的思维风格是结构化演绎：从宏观战略框架出发，逐层分解为可验证的子命题。"
            "你擅长识别命题成立的充分条件，并构建多条独立的证据链形成交叉验证。"
            "在论证中采用'战略叙事'手法——将分散的事实编织成连贯的战略逻辑。"
            "始终站在决策者视角，说明该命题为何在战略上是必要且可行的。"
            "具体要求：（1）至少提出3条独立证据链，每条必须引用具体数据源；"
            "（2）预判潜在反驳并在论证中主动设置防线；"
            "（3）标注论证的脆弱点——即你认为对手最容易攻击的薄弱环节。"
            + _EVIDENCE_CITATION
            + _CROSS_DOMAIN
            + _HILL_CLIMBING
        ),
        "challenger": (
            "你是【风险挑战者🔴】，负责系统性地解构命题的支撑论据。"
            "你的思维风格是批判性分析：采用'红队思维'，专门寻找论证中的逻辑漏洞、"
            "证据缺陷和隐含假设。你擅长运用'事前验尸法'——假设命题已经失败，"
            "逆向推导失败的可能路径。对每个论点追问三个层次：证据是否充分？"
            "推理是否严密？结论是否唯一？你的目标不是为反对而反对，而是通过"
            "严格的压力测试暴露命题的真实脆弱性。"
            "具体要求：（1）对每条立论论点给出具体的质疑理由和替代解释；"
            "（2）指出论证中的逻辑跳跃和未被考虑的负面情景；"
            "（3）提供至少1个反面案例或反面数据来动摇支持论点；"
            "（4）评估对手论证中最薄弱的环节，并解释为什么这足以动摇整个论证。"
            + _EVIDENCE_CITATION
            + _CROSS_DOMAIN
            + _HILL_CLIMBING
            + _BIAS_DETECTION
        ),
        "arbitrator": (
            "你是【首席仲裁官⚖️】，负责在充分听取各方论证后做出最终裁决。"
            "你的思维风格是辩证综合：不简单地选择胜出方，而是通过'辩证扬弃'——"
            "保留各方论证中经得起检验的部分，剔除被有效反驳的部分，"
            "在此基础上构建更高层次的综合判断。你必须：（1）评估每条证据链的可靠性；"
            "（2）权衡不同维度分析的权重；（3）明确裁决的置信区间；"
            "（4）指出在什么条件下裁决可能需要修正。"
            "你的裁决应当体现'认知谦逊'——对不确定性保持诚实。"
            "具体裁决要求：（1）逐条回应各专家的核心论点，说明哪些被采纳、哪些被拒绝及原因；"
            "（2）识别各领域专家之间的共识点和分歧点，分歧处必须给出你的权重分配理由；"
            "（3）提供一个'条件矩阵'——列出在什么条件下裁决应翻转；"
            "（4）给出明确的行动建议：推荐方案、备选方案和应规避的方案。"
            + _EVIDENCE_CITATION
            + _CROSS_DOMAIN
            + _RELIABILITY_SCORING
            + _BLIND_SPOT_DETECTION
        ),
        # ── 专业分析角色 ──────────────────────────────────────
        "intel_analyst": (
            "你是【情报分析师🔍】，负责为辩论提供情报基础的事实核查与来源评估。"
            "你的思维风格是结构化情报分析（SIA）：采用A-B-C来源分级体系评估信息可靠性，"
            "区分硬情报（已验证事实）与软情报（未验证传闻）。你擅长识别情报盲区——"
            "哪些关键信息缺失？信息不对称在哪里？对手可能拥有哪些我们不知道的信息？"
            "在辩论中，你的核心职能是为其他专家的论点提供情报可信度评估，"
            "并对矛盾信息进行交叉验证。"
            "引用情报时必须标注来源类型（OSINT/HUMINT/SIGINT/公开报告）和时效性。"
            "具体要求：（1）对每位专家引用的数据源给出可信度评分（高/中/低）及理由；"
            "（2）识别论证中的'信息黑洞'——关键决策变量缺乏可靠情报的领域；"
            "（3）在修订轮中对挑战者指出的事实性错误进行核实；"
            "（4）提供情报来源的时间线，标注信息是否过时。" + _CROSS_DOMAIN + _HILL_CLIMBING
        ),
        "geo_expert": (
            "你是【地缘政治专家🌍】，负责从地理、联盟体系和国际秩序维度分析命题。"
            "你的思维风格是地缘战略推演：运用麦金德心脏地带论、马汉海权论、"
            "斯皮克曼边缘地带论等经典地缘政治框架，结合当代多极化现实进行分析。"
            "你特别关注：（1）关键地理节点（海峡、通道、战略要地）的影响；"
            "（2）联盟体系的可靠性与约束（如北约第五条款、双边安全条约）；"
            "（3）国际制度框架（联合国安理会、WTO、区域组织）的制约作用；"
            "（4）非国家行为体（跨国公司、NGO、恐怖组织）的地缘影响力。"
            "分析中必须引用具体的地缘历史先例和当前联盟承诺。"
            "具体要求：（1）引用至少2个可类比的历史地缘事件及其结局；"
            "（2）分析关键国家的立场和利益诉求，说明为什么它们会采取特定行动；"
            "（3）评估地理因素对军事行动和经济活动的具体约束；"
            "（4）在修订轮中回应经济分析师和军事战略家的跨域观点。"
            + _EVIDENCE_CITATION
            + _CROSS_DOMAIN
            + _HILL_CLIMBING
        ),
        "econ_analyst": (
            "你是【经济分析师💰】，负责评估命题的宏观经济影响和经济可行性。"
            "你的思维风格是量化成本收益分析：用具体经济指标（GDP、CPI、失业率、"
            "贸易差额、外汇储备、债务率等）支撑每个论点。你擅长分析："
            "（1）直接经济成本与机会成本；（2）供应链脆弱性与关键依赖；"
            "（3）制裁/贸易战的传导效应；（4）金融市场预期与风险溢价变化；"
            "（5）长期结构性影响与短期周期性波动的区别。"
            "你的分析必须考虑地缘政治对经济的外溢效应——例如安全风险如何影响资本流动、"
            "能源价格如何传导至通胀。引用数据时注明时间范围和数据来源。"
            "具体要求：（1）给出具体的数字估算（如GDP影响百分点、贸易额变化），不得仅定性描述；"
            "（2）分析至少2种不同情景下的经济影响（乐观/悲观/基准）；"
            "（3）识别经济脆弱点——哪些变量的微小变化会导致结论翻转；"
            "（4）在修订轮中回应地缘政治专家和军事战略家提出的经济约束。"
            + _EVIDENCE_CITATION
            + _HILL_CLIMBING
        ),
        "military_strategist": (
            "你是【军事战略家⚔️】，负责从军事力量平衡和作战可行性角度评估命题。"
            "你的思维风格是兵棋推演式分析：评估（1）双方军事力量对比（兵力、装备、"
            "训练、指挥体系）；（2）地理环境对作战的影响；（3）后勤保障能力与持续作战能力；"
            "（4）技术代差与不对称作战可能性；（5）核威慑与升级风险。"
            "你擅长引用具体战例进行类比分析（如海湾战争的后勤教训、"
            "俄乌冲突的无人机战术演变），并评估军事选项的政治后果。"
            "分析中必须区分'军事可行性'与'政治可接受性'——军事上可行的方案"
            "政治上未必可接受。"
            "具体要求：（1）引用至少1个具体战例作为类比，说明其与当前情境的相似和不同；"
            "（2）给出力量对比的具体数据（如兵力比、装备数量对比）；"
            "（3）评估至少2种军事方案的可行性、风险和代价；"
            "（4）在修订轮中回应经济分析师的成本约束和技术前瞻者的技术代差评估。"
            + _EVIDENCE_CITATION
            + _CROSS_DOMAIN
            + _HILL_CLIMBING
        ),
        "tech_foresight": (
            "你是【技术前瞻者🔮】，负责评估技术发展趋势对命题的影响。"
            "你的思维风格是技术成熟度曲线（Gartner Hype Cycle）与S曲线分析相结合："
            "评估关键技术的当前TRL（技术就绪水平）、发展瓶颈和突破时间窗口。"
            "你关注：（1）颠覆性技术（AI、量子计算、高超音速武器）对现有格局的冲击；"
            "（2）技术封锁与自主可控的博弈；（3）军民融合技术的扩散效应；"
            "（4）网络空间与信息战的新维度。"
            "你的分析必须区分'技术可能性'与'工程可行性'与'规模化部署时间线'。"
            "引用具体的研发项目进展、专利数据和技术指标。"
            "具体要求：（1）给出关键技术的TRL等级和预计突破时间窗口（如'量子密码破解：TRL 3，预计5-10年'）；"
            "（2）引用具体的研发项目、专利数量或测试数据作为依据；"
            "（3）评估技术封锁对命题相关方的具体影响（如芯片禁运对军事现代化的延迟效应）；"
            "（4）在修订轮中回应军事战略家的装备评估和社会影响评估师的技术扩散关切。"
            + _EVIDENCE_CITATION
            + _CROSS_DOMAIN
            + _HILL_CLIMBING
        ),
        "social_impact": (
            "你是【社会影响评估师👥】，负责评估命题的社会维度影响。"
            "你的思维风格是社会系统动力学：分析（1）公众舆论与政治合法性；"
            "（2）社会稳定与治理韧性；（3）人口结构变化的长期影响；"
            "（4）信息环境与认知战的影响；（5）人道主义关切与国际规范。"
            "你擅长识别'社会脆弱性指标'——哪些社会因素可能成为决策的约束条件或放大器？"
            "分析中引用民调数据、社会运动案例、人口统计数据和历史社会危机案例。"
            "特别关注技术变革对社会结构的冲击以及代际价值观差异对政策执行的影响。"
            "具体要求：（1）引用具体的民调数据、社会运动案例或人口统计趋势；"
            "（2）评估命题对至少2个不同社会群体（如青年/老年、城市/农村）的差异化影响；"
            "（3）识别社会临界点——什么情况下公众舆论可能发生急剧转向；"
            "（4）在修订轮中回应经济分析师的分配效应和技术前瞻者的就业影响。"
            + _EVIDENCE_CITATION
            + _CROSS_DOMAIN
            + _HILL_CLIMBING
        ),
    }
    return prompts.get(
        role,
        (
            "你是一名客观分析员，负责基于证据评估命题。"
            "要求：（1）引用具体数据和来源（报告名称、机构、日期），禁止模糊表述；"
            "（2）考虑跨领域关联因素，标注跨域因果链；"
            "（3）在收到质疑时逐条回应，标注✅保留/❌放弃/🔄修正状态，主动修正和完善分析；"
            "（4）给出置信度及变化原因。" + _EVIDENCE_CITATION + _CROSS_DOMAIN + _HILL_CLIMBING
        ),
    )


round_plan = [
    # ── Round 1: 立论 ──────────────────────────────────────
    (
        1,
        "advocate",
        "【第1轮·立论】战略支持者：请提出支持该命题的核心证据链和战略逻辑框架。要求：至少3条独立论证，每条引用具体数据源，标注最脆弱的论点以便后续防御。",
    ),
    (
        1,
        "intel_analyst",
        "【第1轮·立论】情报分析师：请提供情报支撑，评估关键信息的可靠性和情报盲区。要求：对已知事实进行A/B/C来源分级，识别至少2个关键情报盲区。",
    ),
    (
        1,
        "geo_expert",
        "【第1轮·立论】地缘政治专家：请从地理、联盟体系和国际秩序维度分析命题的可行性。要求：引用至少2个可类比的历史地缘事件，分析关键国家的利益诉求。",
    ),
    (
        1,
        "econ_analyst",
        "【第1轮·立论】经济分析师：请评估命题的宏观经济影响、成本收益和经济约束。要求：给出具体数字估算（GDP、贸易额等），分析乐观/悲观/基准三种情景。",
    ),
    (
        1,
        "military_strategist",
        "【第1轮·立论】军事战略家：请从军事力量平衡和作战可行性角度评估命题。要求：引用具体战例类比，给出力量对比数据，评估至少2种军事方案。",
    ),
    (
        1,
        "tech_foresight",
        "【第1轮·立论】技术前瞻者：请评估相关技术发展趋势及其对命题的影响。要求：标注关键技术TRL等级和突破时间窗口，引用具体研发项目或专利数据。",
    ),
    (
        1,
        "social_impact",
        "【第1轮·立论】社会影响评估师：请评估命题的社会维度影响和公众舆论基础。要求：引用民调/人口数据，评估对不同社会群体的差异化影响，识别社会临界点。",
    ),
    # ── Round 2: 质询 ──────────────────────────────────────
    (
        2,
        "challenger",
        "【第2轮·质询】风险挑战者：请针对所有立论进行系统性质疑。要求：对每条论点给出具体质疑理由，提供至少1个反面案例或数据，指出逻辑跳跃和未考虑的负面情景。",
    ),
    (
        2,
        "intel_analyst",
        "【第2轮·质询】情报分析师：请对立论中的事实声明进行情报核实。要求：对各专家引用的数据源给出可信度评分，标注信息黑洞和过时情报，验证矛盾信息。",
    ),
    # ── Round 3: 修订 ──────────────────────────────────────
    (
        3,
        "advocate",
        "【第3轮·修订】战略支持者：请根据质询反馈修订和完善你的核心论证。要求：逐条回应挑战者的质疑，标注✅保留/❌放弃/🔄修正的论点状态，补充新证据强化薄弱环节。",
    ),
    (
        3,
        "geo_expert",
        "【第3轮·修订】地缘政治专家：请根据质询修订地缘政治分析。要求：回应经济分析师和军事战略家的跨域观点，修正被质疑的历史类比，补充地缘约束分析。",
    ),
    (
        3,
        "econ_analyst",
        "【第3轮·修订】经济分析师：请根据质询修订经济分析。要求：修正被质疑的数据和估算，回应地缘政治专家提出的经济约束，更新情景分析的置信度。",
    ),
    (
        3,
        "military_strategist",
        "【第3轮·修订】军事战略家：请根据质询修订军事评估。要求：回应经济分析师的成本约束和技术前瞻者的代差评估，强化或修正关键军事判断。",
    ),
    (
        3,
        "tech_foresight",
        "【第3轮·修订】技术前瞻者：请根据质询修订技术评估。要求：更新被质疑的时间线和TRL等级，回应军事战略家的装备评估，修正技术封锁影响分析。",
    ),
    (
        3,
        "social_impact",
        "【第3轮·修订】社会影响评估师：请根据质询修订社会影响分析。要求：回应经济分析师的分配效应和技术前瞻者的就业影响，修正社会临界点评估。",
    ),
    # ── Round 4: 仲裁 ──────────────────────────────────────
    (
        4,
        "arbitrator",
        "【第4轮·仲裁】首席仲裁官：请基于全部论证历史做出最终裁决。要求：逐条回应各专家核心论点（采纳/拒绝及理由），识别共识点和分歧点，提供条件矩阵和行动建议（推荐/备选/规避方案）。",
    ),
]


def build_round_plan(
    custom_agents: list[dict[str, Any]] | None = None,
    mode: str = "full",
    domain_id: str | None = None,
    topic: str | None = None,
    context: str | None = None,
    evidence_count: int = 0,
) -> list[tuple[int, str, str]]:
    """Build a round plan with optional fast mode and domain-specific role selection.

    Args:
        custom_agents: Additional custom agents to include
        mode: "full" (4 rounds, 7+ roles) or "fast" (2 rounds, 3 roles)
        domain_id: Domain hint for role selection ("corporate", "military", "auto")
        topic/context/evidence_count: Optional signals used to choose the debate depth.
    """
    if mode == "fast":
        return _build_fast_round_plan(custom_agents, domain_id)
    return _build_full_round_plan(custom_agents, domain_id, topic, context, evidence_count)


def _build_full_round_plan(
    custom_agents: list[dict[str, Any]] | None = None,
    domain_id: str | None = None,
    topic: str | None = None,
    context: str | None = None,
    evidence_count: int = 0,
) -> list[tuple[int, str, str]]:
    """Build a full debate with dynamic depth and cross-examination."""
    complexity = infer_debate_complexity(topic, context, evidence_count)
    selected_roles = select_roles_for_domain(domain_id, complexity=complexity)

    if complexity == "complex":
        plan = list(round_plan)
        _apply_cross_exam_protocol(plan)
        _add_custom_agents(plan, custom_agents, domain_id)
        return plan

    round1 = [role for role in selected_roles if role not in {"challenger", "arbitrator"}]
    expert_roles = [role for role in round1 if role not in {"advocate", "intel_analyst"}]
    plan = []
    for role in round1:
        plan.append((1, role, _round1_instruction(role)))

    cross_roles = ["challenger"]
    if complexity == "standard" and "intel_analyst" in selected_roles:
        cross_roles.append("intel_analyst")
    for role in cross_roles:
        plan.append((2, role, _cross_exam_instruction(role, round1)))

    revision_roles = ["advocate", *expert_roles[:2]]
    for role in dict.fromkeys(revision_roles):
        plan.append((3, role, _revision_instruction(role)))

    plan.append((4, "arbitrator", _arbitration_instruction()))
    _add_custom_agents(plan, custom_agents, domain_id)
    return plan


def _build_fast_round_plan(
    custom_agents: list[dict[str, Any]] | None = None,
    domain_id: str | None = None,
) -> list[tuple[int, str, str]]:
    """Fast 2-round debate with 3 key roles — saves ~60% tokens.

    Round 1: advocate + challenger + most relevant expert
    Round 2: arbitrator
    """
    # Select the most relevant expert based on domain
    domain_experts = {
        "corporate": "econ_analyst",
        "military": "military_strategist",
        "auto": "intel_analyst",
    }
    expert = domain_experts.get(domain_id, "intel_analyst") if domain_id else "intel_analyst"

    expert_labels = {
        "intel_analyst": "情报分析师",
        "geo_expert": "地缘政治专家",
        "econ_analyst": "经济分析师",
        "military_strategist": "军事战略家",
        "tech_foresight": "技术前瞻者",
        "social_impact": "社会影响评估师",
    }
    expert_label = expert_labels.get(expert, "情报分析师")

    fast_plan = [
        (
            1,
            "advocate",
            "【第1轮·立论】战略支持者：请提出支持该命题的核心证据链。要求：至少2条独立论证，每条引用具体数据源。",
        ),
        (
            1,
            expert,
            f"【第1轮·立论】{expert_label}：请从专业角度评估命题并提供分析。要求：引用具体数据和来源，提供专业判断。",
        ),
        (
            2,
            "challenger",
            "【第2轮·质询+仲裁】风险挑战者：请对立论进行系统性质疑，然后综合评判。要求：对每条论点给出质疑理由，最终给出综合裁决和建议。",
        ),
    ]

    # Insert custom agents
    for ca in _sort_custom_agents(custom_agents, domain_id):
        role_key = ca["role_key"]
        name = ca["name"]
        icon = ca.get("icon", "[custom]")
        description = ca.get("description", "")
        desc_brief = description[:200] + ("..." if len(description) > 200 else "")
        fast_plan.insert(-1, (1, role_key, f"【第1轮·立论】{icon} {name}：{desc_brief}"))

    return fast_plan


def infer_debate_complexity(
    topic: str | None,
    context: str | None = None,
    evidence_count: int = 0,
) -> str:
    """Infer simple / standard / complex debate depth from available signals."""
    text = f"{topic or ''}\n{context or ''}".lower()
    score = 0
    if len(text) > 1200:
        score += 2
    elif len(text) > 450:
        score += 1
    if evidence_count >= 6:
        score += 2
    elif evidence_count >= 3:
        score += 1
    high_stakes_terms = {
        "war",
        "military",
        "sanction",
        "regulation",
        "lawsuit",
        "bankruptcy",
        "merger",
        "acquisition",
        "security",
        "supply chain",
        "escalation",
        "geopolitical",
        "制裁",
        "战争",
        "军事",
        "监管",
        "诉讼",
        "破产",
        "并购",
        "安全",
        "供应链",
        "升级",
        "地缘",
    }
    if any(term in text for term in high_stakes_terms):
        score += 1
    if any(term in text for term in {"conflict", "contradict", "overturn", "反转", "矛盾", "推翻"}):
        score += 1
    if score >= 4:
        return "complex"
    if score >= 2:
        return "standard"
    return "simple"


def select_roles_for_domain(domain_id: str | None, complexity: str = "standard") -> list[str]:
    """Return the recommended roles for a given domain.

    Used for dynamic role selection — avoids running all 7 roles
    when only 3-4 are relevant.
    """
    # Core roles always included
    core = ["advocate", "challenger", "arbitrator"]

    if complexity == "simple":
        domain_roles = {
            "corporate": core + ["econ_analyst"],
            "military": core + ["military_strategist", "intel_analyst"],
            "auto": core + ["intel_analyst"],
        }
    elif complexity == "complex":
        domain_roles = {
            "corporate": core + ["intel_analyst", "geo_expert", "econ_analyst", "tech_foresight", "social_impact"],
            "military": core + ["intel_analyst", "geo_expert", "econ_analyst", "military_strategist", "tech_foresight", "social_impact"],
            "auto": core + ["intel_analyst", "geo_expert", "econ_analyst", "military_strategist", "tech_foresight", "social_impact"],
        }
    else:
        domain_roles = {
            "corporate": core + ["intel_analyst", "econ_analyst", "tech_foresight", "social_impact"],
            "military": core + ["geo_expert", "military_strategist", "intel_analyst"],
            "auto": core + ["intel_analyst", "econ_analyst", "geo_expert"],
        }
    return domain_roles.get(domain_id or "auto", list(core) + ["intel_analyst"])


def _round1_instruction(role: str) -> str:
    labels = {
        "advocate": "战略支持者：请提出支持该命题的核心证据链和战略逻辑框架。",
        "intel_analyst": "情报分析师：请核实事实基础、来源可靠性和关键情报盲区。",
        "geo_expert": "地缘政治专家：请评估地理、联盟体系和国际秩序约束。",
        "econ_analyst": "经济分析师：请评估宏观经济影响、成本收益和资源约束。",
        "military_strategist": "军事战略家：请评估力量平衡、行动可行性和升级风险。",
        "tech_foresight": "技术前瞻者：请评估技术成熟度、工程可行性和时间窗口。",
        "social_impact": "社会影响评估师：请评估公众舆论、治理韧性和利益相关方影响。",
    }
    return f"【第1轮·立论】{labels.get(role, role + '：请提供独立专业判断。')}要求：给出可验证证据、关键假设、最脆弱论点和置信度。"


def _cross_exam_instruction(role: str, target_roles: list[str]) -> str:
    targets = "、".join(target_roles)
    return (
        f"【第2轮·交叉质询】{role}：请直接质询这些前序角色：{targets}。"
        "必须在 rebuttals 中输出结构化质询项，每项包含 target_role、question、counter 或 expected_answer。"
        "同时回答 context 中点名给你的质询，逐条说明你接受、反驳或需要更多证据。"
        "不要只概括对方观点；要明确指出哪一条论证被挑战、挑战依据和可能改变结论的证据条件。"
    )


def _revision_instruction(role: str) -> str:
    return (
        f"【第3轮·修订】{role}：请根据交叉质询结果修订你的立场。"
        "必须逐条回应与你相关的 question，标注✅保留/❌放弃/🔄修正，"
        "并说明置信度是否变化以及变化原因。"
    )


def _arbitration_instruction() -> str:
    return (
        "【第4轮·仲裁】首席仲裁官：请基于立论、交叉质询和修订结果做最终裁决。"
        "要求识别共识点、不可消除分歧、需要用户继续监控的触发条件，并给出行动建议。"
    )


def _apply_cross_exam_protocol(plan: list[tuple[int, str, str]]) -> None:
    for idx, (round_number, role, instruction) in enumerate(plan):
        if round_number == 2:
            targets = ["advocate", "geo_expert", "econ_analyst", "military_strategist", "tech_foresight", "social_impact"]
            plan[idx] = (round_number, role, f"{instruction}\n\n{_cross_exam_instruction(role, targets)}")
        elif round_number == 3:
            plan[idx] = (round_number, role, f"{instruction}\n\n{_revision_instruction(role)}")


def _add_custom_agents(
    plan: list[tuple[int, str, str]],
    custom_agents: list[dict[str, Any]] | None,
    domain_id: str | None = None,
) -> None:
    """Insert custom agents into round 1 and round 3."""
    for ca in _sort_custom_agents(custom_agents, domain_id):
        role_key = ca["role_key"]
        name = ca["name"]
        icon = ca.get("icon", "[custom]")
        description = ca.get("description", "")
        desc_brief = description[:200] + ("..." if len(description) > 200 else "")
        domain_hint = _custom_agent_domain_hint(ca, domain_id)
        role_intro = "independent specialist" if domain_hint == "independent" else domain_hint
        plan.append(
            (1, role_key, f"【第1轮·立论】{icon} {name}：{desc_brief}"),
        )
        plan.append(
            (
                3,
                role_key,
                f"【第3轮·修订】{icon} {name}：请作为{role_intro}根据质询反馈修订你的分析。要求：回应其他角色的跨域观点，修正被质疑的论点。",
            ),
        )
    plan.sort(key=lambda item: _round_plan_sort_key(item[0], item[1]))


def _sort_custom_agents(
    custom_agents: list[dict[str, Any]] | None,
    domain_id: str | None,
) -> list[dict[str, Any]]:
    def priority_value(agent: dict[str, Any]) -> int:
        raw = agent.get("priority", 50)
        if isinstance(raw, str):
            return {"high": 10, "medium": 50, "low": 90}.get(raw.lower(), 50)
        try:
            return int(raw)
        except (TypeError, ValueError):
            return 50

    def domain_rank(agent: dict[str, Any]) -> int:
        domains = {
            str(item).lower()
            for item in (
                agent.get("domains")
                or agent.get("domain_tags")
                or agent.get("tags")
                or []
            )
        }
        if not domains:
            return 1
        if domain_id and domain_id.lower() in domains:
            return 0
        if domains & {"all", "auto", "general", "global"}:
            return 1
        return 2

    return sorted(
        custom_agents or [],
        key=lambda agent: (
            domain_rank(agent),
            priority_value(agent),
            str(agent.get("name") or agent.get("role_key") or ""),
        ),
    )


def _custom_agent_domain_hint(agent: dict[str, Any], domain_id: str | None) -> str:
    domains = [
        str(item)
        for item in (
            agent.get("domains")
            or agent.get("domain_tags")
            or agent.get("tags")
            or []
        )
    ]
    if domain_id and domain_id in domains:
        return f"{domain_id} specialist"
    if domains:
        return ", ".join(domains[:3]) + " specialist"
    return "independent"


def _round_plan_sort_key(round_number: int, role: str) -> tuple[int, int, str]:
    role_order = {
        1: [
            "advocate",
            "intel_analyst",
            "geo_expert",
            "econ_analyst",
            "military_strategist",
            "tech_foresight",
            "social_impact",
        ],
        2: ["challenger", "intel_analyst"],
        3: [
            "advocate",
            "geo_expert",
            "econ_analyst",
            "military_strategist",
            "tech_foresight",
            "social_impact",
        ],
        4: ["arbitrator"],
    }
    order = role_order.get(round_number, [])
    return (
        round_number,
        order.index(role) if role in order else 80,
        role,
    )
