"""MBTI 人格 → 行为旋钮映射。

为什么这么设计:逐个硬编码 16 型会僵化且无法组合。这里把 MBTI 拆成 4 个独立轴,
每轴映射到一个 [0,1] 的行为旋钮,再由轴 + 年龄派生出更细的倾向。这些旋钮后续会
注入 agent 决策的 prompt,作为"性格参数";其中 assertiveness 专门用于制造性格摩擦,
对冲 instruction-tuned 模型"过度礼貌、没主见"的通病(见 PRD §4.9)。

注:下方派生公式是 v0 启发式,待真实仿真后校准(PRD §12 P0)。
"""

from __future__ import annotations

from dataclasses import asdict, dataclass

# 每轴 (高值字母, 低值字母):E/N/F/J 取高,I/S/T/P 取低
_AXES: tuple[tuple[str, str], ...] = (("E", "I"), ("N", "S"), ("F", "T"), ("J", "P"))
_HIGH = 0.8  # 留 0.2 头部空间,避免极端值把后续公式顶到边界
_LOW = 0.2


@dataclass(frozen=True)
class BehaviorProfile:
    """agent 的性格行为旋钮,全部归一化到 [0,1]。"""

    social_initiative: float  # E/I:主动社交 vs 独处
    novelty_seeking: float  # N/S:探索新事物 vs 守常
    agreeableness: float  # F/T:顾及他人 vs 坚持逻辑己见
    routine_strictness: float  # J/P:作息/计划规律 vs 随性
    # —— 派生旋钮 ——
    assertiveness: float  # 冲突中坚持己见、不轻易妥协(制造摩擦)
    risk_tolerance: float  # 冒险/尝鲜倾向
    night_owl: float  # 夜猫子倾向(0=早睡早起,1=熬夜)

    def to_dict(self) -> dict[str, float]:
        return asdict(self)


def normalize_mbti(mbti: str) -> str:
    """校验并规整 MBTI 字符串(大写去空格),非法抛 ValueError。"""
    if not isinstance(mbti, str):
        raise ValueError(f"MBTI 必须是字符串,得到 {type(mbti)!r}")
    code = mbti.strip().upper()
    if len(code) != 4:
        raise ValueError(f"MBTI 必须是 4 个字母,得到 {mbti!r}")
    for letter, (hi, lo) in zip(code, _AXES):
        if letter not in (hi, lo):
            raise ValueError(f"非法 MBTI {mbti!r}:字母 {letter!r} 不属于 {hi}/{lo}")
    return code


def _clamp01(x: float) -> float:
    return max(0.0, min(1.0, x))


def mbti_to_behavior(mbti: str, age: int = 30) -> BehaviorProfile:
    """把 MBTI + 年龄映射成行为旋钮。年龄主要影响作息与冒险倾向。"""
    code = normalize_mbti(mbti)
    is_e, is_n, is_f, is_j = (
        code[0] == "E",
        code[1] == "N",
        code[2] == "F",
        code[3] == "J",
    )

    social_initiative = _HIGH if is_e else _LOW
    novelty_seeking = _HIGH if is_n else _LOW
    agreeableness = _HIGH if is_f else _LOW
    routine_strictness = _HIGH if is_j else _LOW

    # 年龄因子:越年轻越爱冒险/熬夜。40 岁为中点,归一化到 [0,1]
    youth = _clamp01((40 - age) / 50.0)

    # 坚持己见:低亲和(T)+ 高主动(E)→ 更强
    assertiveness = _clamp01(
        0.4 + 0.4 * (1 - agreeableness) + 0.2 * (social_initiative - 0.5) * 2
    )
    # 冒险:随性(低 routine)+ 尝鲜(N)+ 年轻
    risk_tolerance = _clamp01(
        0.25 + 0.35 * (1 - routine_strictness) + 0.2 * novelty_seeking + 0.2 * youth
    )
    # 夜猫子:随性 + 年轻;J 型偏早睡
    night_owl = _clamp01(0.2 + 0.4 * (1 - routine_strictness) + 0.4 * youth)

    return BehaviorProfile(
        social_initiative=social_initiative,
        novelty_seeking=novelty_seeking,
        agreeableness=agreeableness,
        routine_strictness=routine_strictness,
        assertiveness=round(assertiveness, 3),
        risk_tolerance=round(risk_tolerance, 3),
        night_owl=round(night_owl, 3),
    )
