import HelpOutlineOutlined from "@mui/icons-material/HelpOutlineOutlined";
import Tooltip from "@mui/material/Tooltip";

// 字段说明集中维护，避免同一参数在不同分区出现互相矛盾的运营口径。
// 每条都回答“控制什么、调大会怎样、发布时受什么限制”，Tooltip 只负责展示，不参与表单值或校验。
const FIELD_HELP = {
    app_code: {
        summary: "决定这套幸运礼物规则属于哪个 App。不同 App 的配置、抽奖记录和奖池余额彼此隔离。",
        adjust: "选择错 App 会把新版本发布到另一套业务数据中；编辑已有配置时不能在这里迁移 App。",
        constraint: "发布前确认应用与实际送礼入口一致。",
    },
    pool_id: {
        summary: "决定礼物使用哪一组规则和哪一本奖池账。同一 App 内，送礼接口会按这个 ID 找到配置。",
        adjust: "改成新 ID 会创建一组新的规则，不会搬走旧奖池里的金币，也不会改写旧抽奖记录。",
        constraint: "fixed_v2 与 dynamic_v3 即使使用相同 ID，也会分别记账；ID 必须与礼物接入配置一致。",
    },
    strategy_version: {
        summary:
            "选择开奖方式。fixed_v2 保留旧版固定规则；dynamic_v3 会根据奖池余额、充值阶段、大奖条件和返奖上限动态调整。",
        adjust: "切到 dynamic_v3 会载入安全草稿并保持停用，确认全部金额口径后再发布。",
        constraint: "dynamic_v3 使用独立奖池且从 0 开始，发布规则不会自动注入金币。",
    },
    enabled: {
        summary: "决定新版本发布后是否立即用于真实送礼。关闭时规则仍会保存，但不会触发幸运开奖。",
        adjust: "首次配置或大幅调整时可先保持停用，核对资金比例和上限后再启用。",
        constraint: "启用 dynamic_v3 前必须填完三项大奖 RTP 触发线、累计消费门槛和所有返奖上限。",
    },
    target_rtp_percent: {
        summary: "表示基础奖档的配置目标。例如填 98，代表各阶段的“倍率 × 基础概率”预计返还约为 98%。",
        adjust: "修改后，页面会重新分配各档基础概率；调高通常增加基础返奖，调低则相反。",
        constraint:
            "只能填 10%–99%，进入奖池的比例不能低于它。水位、充值加成、大奖和上限都会改变实际结果，它不是线上返还承诺。",
    },
    control_band_percent: {
        summary: "允许每个用户阶段的奖档组合与平均返还目标存在多大偏差。",
        adjust: "调大更容易通过发布，但实际返还可能离目标更远；调小更严格，也更难配置出可发布的倍率组合。",
        constraint: "必须大于 0 且不超过 5 个百分点，不会强行补偿单次或单个用户的结果。",
    },
    pool_rate_percent: {
        summary: "每笔幸运礼物消费中，有多少金币进入公共奖池，后续中奖从这里支付。",
        adjust: "调高会让奖池补充更快；调低会留出更多平台或收礼份额，但奖池更容易不足。",
        constraint: "不能低于平均返还目标；V3 中它与平台、收礼份额合计必须等于 100%，按整数金币向下取整。",
    },
    profit_rate_percent: {
        summary: "V3 每笔消费中直接计为平台收入的比例，不进入奖池，也不发给收礼对象。",
        adjust: "调高会增加平台份额，但必须相应减少奖池或收礼份额。",
        constraint:
            "不能为负数；V3 三项资金比例必须精确合计 100%，整数拆分后不能整除的余数也计入平台；V2 不使用该比例开奖。",
    },
    settlement_window_wager: {
        summary:
            "V3 按实际消费金币累计一轮大盘 RTP；某一抽跨过门槛时仍归旧轮，下一抽才进入新轮。大盘结算并重置时，也会按完全相同的起止时间结算每个用户的本轮 RTP。",
        adjust: "调大后大盘和用户本轮 RTP 都会更久才结算；调小后两者都会更频繁结算。V2 会先按礼物参考金额换算成窗口抽数。",
        constraint: "必须大于 0。新一轮还没有消费样本时，不会触发返还偏低大奖。",
    },
    gift_price_reference: {
        summary: "V2 用它把累计消费换算成“折算次数”，同时计算用户阶段和每轮窗口的抽数；它不是礼物真实售价。",
        adjust: "调高后，同样消费折算成更少次数，用户升级更慢且 V2 窗口抽数更少；调低则相反。",
        constraint: "必须大于 0。V3 的窗口直接看真实金币流水，用户阶段看 7 日和 30 日充值，不用它做这两项判断。",
    },
    novice_max_equivalent_draws: {
        summary: "fixed_v2 中，用户累计消费折算次数不超过这个值时使用新手奖档。",
        adjust: "调高会让用户在新手阶段停留更久；调低会更早进入普通阶段。",
        constraint: "不能小于 0，也不能高于普通阶段上限；dynamic_v3 不使用这个值分层。",
    },
    normal_max_equivalent_draws: {
        summary: "fixed_v2 中，用户超过新手上限后，不超过这个值时使用普通奖档；再超过才进入高阶奖档。",
        adjust: "调高会推迟进入高阶阶段；调低会让用户更早进入高阶阶段。",
        constraint: "不能小于新手阶段上限；dynamic_v3 不使用这个值分层。",
    },
    initial_pool_coins: {
        summary: "表示发布规则时是否顺便给奖池放入启动金币。dynamic_v3 已改为不允许规则自动注资。",
        adjust: "该值固定为 0 且不可编辑，避免每次发新版本重复增加奖池余额。",
        constraint: "需要启动资金时，请在配置列表使用“添加水位”，该操作会保留独立审计记录。",
    },
    loss_streak_guarantee: {
        summary:
            "同一用户在当前礼物奖池连续多少次返还为 0 后，下一次普通开奖会先去掉 0 倍档。填 5 表示连续 5 次为 0 后，第 6 次普通开奖尝试正奖。",
        adjust: "调小会更快触发体验保护；调大会降低触发频率并减少奖池压力。",
        constraint: "必须大于 0；奖池不足或任一返奖上限不足时仍可返回 0，不会透支。",
    },
    low_watermark_coins: {
        summary: "本抽应进入奖池的金币先入账；入账后余额低于这个值时，系统会降低普通开奖中非 0 倍档的基础机会。",
        adjust: "调高会更早开始保护奖池；调低会让正常中奖节奏保持更久。",
        constraint: "必须大于 0，并且小于高余额分界线。",
    },
    low_water_nonzero_factor_percent: {
        summary: "奖池处于低余额时，普通开奖中非 0 倍档的基础机会保留多少。填 70 表示按原来的 70% 参与计算。",
        adjust: "调低更保护奖池但中奖更少；调高更接近正常节奏但消耗更快。",
        constraint: "必须大于 0% 且小于 100%，0 倍档接收剩余概率；它不会直接提高或降低两种大奖的触发机会。",
    },
    high_watermark_coins: {
        summary: "本抽应进入奖池的金币先入账；入账后余额高于这个值时，系统会提高普通开奖中非 0 倍档的基础机会。",
        adjust: "调低会更早提高中奖机会；调高会要求奖池积累更多后才加速返奖。",
        constraint: "必须严格大于低余额分界线；刚好等于该值时仍按正常状态处理。",
    },
    high_water_nonzero_factor_percent: {
        summary: "奖池处于高余额时，普通开奖中非 0 倍档的基础机会放大到多少。填 130 表示变为原来的 130%。",
        adjust: "调高会更快释放奖池、增加正奖机会；调低则更接近正常节奏。",
        constraint: "必须大于 100%；放大后会重新归一化，并继续受奖池和返奖上限限制；它不会直接提高大奖触发机会。",
    },
    v2_high_water_boost_threshold_coins: {
        summary:
            "仅 V2 策略生效：本抽应进入奖池的金币先入账；入账后水位高于这个值时进入加权状态，中奖返奖金额按加权系数放大。",
        adjust: "调低会更早触发加速返还；调高会让奖池积累更多后才开始放大返奖。",
        constraint: "填 0 表示关闭；启用时必须大于 0，且加权系数与恢复水位都必须一并配置。",
    },
    v2_high_water_boost_factor_percent: {
        summary:
            "加权状态下中奖返奖金额放大到多少。填 150 表示每笔中奖金额与整体 RTP 都变为原来的 150%（2 倍中奖实付 3 倍）。",
        adjust: "调高会更快消化奖池积压，也会让玩家短期收益更高；调低则更温和。",
        constraint: "必须大于 100% 且不超过 1000%；放大后的返奖仍受奖池支付能力限制，不会透支。",
    },
    v2_high_water_boost_recover_coins: {
        summary:
            "退出加权的水位下沿：一旦触发加权，即使水位回落到阈值以下仍继续放大，直到水位低于这个值才恢复正常返奖。",
        adjust: "调低会让加权持续更久、消化更彻底；调高会更快恢复正常节奏。",
        constraint: "必须大于 0 且不高于加权阈值；与阈值相等表示不留滞回区间。",
    },
    recharge_boost_window_ms: {
        summary: "用户完成一次可验证充值后，普通开奖中非 0 倍档的机会加成持续多久。300000 毫秒等于 5 分钟。",
        adjust: "调大会延长充值后的加成时间；调小会让加成更快结束。",
        constraint: "必须大于 0，生效区间包含充值时刻但不包含结束时刻；外部接入没有可信充值事实，不会触发加成。",
    },
    recharge_boost_factor_percent: {
        summary: "充值加成期间，普通开奖中非 0 倍档的基础机会放大到多少。填 110 表示变为原来的 110%。",
        adjust: "调高会增强充值后的中奖体验，也会增加奖池消耗；调低则更温和。",
        constraint: "必须大于 100%，会与高低余额调整相乘；它只改变普通奖档，不会直接提高大奖触发机会。",
    },
    user_24h_rtp_threshold_percent: {
        summary:
            "每次普通开奖前，系统会把同一用户在当前应用和奖池近 24 小时的实际返奖除以实际消费。结果严格高于这里填写的比例时，才降低普通中奖机会。",
        adjust: "填 150 表示近 24 小时返奖超过消费的 150% 后开始调整。调低会让更多用户更早进入调整，调高则只处理返还明显偏高的用户。",
        constraint:
            "结果刚好等于或低于触发线时立即恢复正常；近 24 小时没有消费时不调整。内部用户和外部接入用户都按各自稳定账号独立统计。",
    },
    user_24h_ordinary_win_factor_percent: {
        summary:
            "用户超过近 24 小时触发线后，普通非 0 倍奖项还保留多少中奖机会。填 70 表示保留原来的 70%，也就是降低 30%，不是降低 70%。",
        adjust: "调低会把更多普通中奖机会转成未中奖，更快压低该用户的短期返还；填 100 表示不减少中奖机会。",
        constraint:
            "必须大于 0% 且不超过 100%。减少的部分全部进入 0 倍档；两种独立大奖不受影响，连续未中奖保护仍优先执行，奖池余额和返奖上限也仍会拦截无法支付的结果。",
    },
    jackpot_multipliers: {
        summary:
            "列出 48 小时亏损补偿和累计消费达标两种大奖策略可能采用的倍数，例如 200、500、1000。它不并入普通奖档概率：大奖未成功才进入普通开奖。",
        adjust: "增加或调高倍数会放大独立大奖金额和资金压力；删除倍数只会减少可选大奖，不会删除普通奖档。",
        constraint:
            "用逗号分隔，必须都大于 1 且从小到大；同倍率可以同时出现在普通奖档中。这里的“独立大奖”是相对普通概率开奖而言；48 小时补偿与累计消费两种大奖策略共享每日大奖总次数、当前奖池、RTP 统计和六项返奖上限。普通命中不计入大奖次数。",
    },
    jackpot_global_rtp_max_percent: {
        summary:
            "两种大奖策略共同使用的大盘安全线。48 小时亏损补偿使用产生资格时的结算轮次；累计消费达标在送礼时读取最新一个已经关闭的大盘结算轮次。实际大盘 RTP 小于或等于这条线时才通过，等于也通过。",
        adjust: "调高会让两种大奖更容易进入候选；调低会要求大盘返还更低才触发。",
        constraint:
            "必须大于 0% 且不超过 100%。没有可用的已结算大盘样本时不会发大奖；奖池余额、每日大奖次数和全部返奖上限也必须同时足够。用户两项 RTP 只属于 48 小时亏损补偿，不限制累计消费达标策略。它不会阻止普通概率命中相同倍率。",
    },
    jackpot_user_round_rtp_max_percent: {
        summary:
            "系统每次重置大盘 RTP 时，会按完全相同的起止时间结算这个用户的本轮 RTP。例如 9 点重置一次、10 点再次重置，10 点结算时就检查用户在 9 点到 10 点这一轮的总返奖 ÷ 总消费。",
        adjust: "调高会让更多本轮返还偏低的用户通过；调低会更严格。用户本轮 RTP 必须严格小于配置值，刚好等于也不通过。",
        constraint:
            "只统计当前 App、当前奖池和同一结算轮次内的实际消费与返奖；本轮没有消费样本时不通过。它必须与近 48 小时条件同时通过，不能只满足其中一项；该条件不限制累计消费达标策略。",
    },
    jackpot_user_48h_rtp_max_percent: {
        summary:
            "判断这个用户近 48 小时是否整体返还偏低。本轮结算时，用户注册已满 48 小时，系统才会把结算时点向前 48 小时内的返奖金币加总，再除以同期实际消费金币；同一 App、奖池下跨房间、主播和设备合并统计，结果严格低于这条线才通过。",
        adjust: "调高会更容易通过，调低会更严格；结果等于配置值也不通过。例如近 48 小时消费 10,000、返奖 9,500，RTP 为 95%，配置 96% 时通过；返奖 9,600、RTP 为 96% 时不通过。",
        constraint:
            "先看注册时间：注册未满 48 小时直接不通过；注册已满 48 小时后，不要求连续玩满 48 小时，只统计窗口内真实发生的消费和返奖。即使前 47 小时都没玩、最近只玩 5 分钟，也用这 5 分钟的数据计算；整段时间没有消费时不通过。它还必须与大盘条件、用户本轮条件同时通过，并受每日大奖次数、奖池余额和所有返奖上限限制；该条件不限制累计消费达标策略。",
    },
    max_jackpot_hits_per_user_day: {
        summary: "限制同一用户在当前奖池的一个 UTC 自然日内，通过 48 小时亏损补偿和累计消费达标两种策略合计最多真正拿到多少次大奖。",
        adjust:
            "调高允许同一用户一天通过独立大奖路径获得更多奖励；调低会更早停止该用户当天的独立大奖，不保证转给其他用户。",
        constraint:
            "必须大于 0；所有独立大奖倍率合计使用这个次数。达到后不再走独立大奖，但仍可按普通概率命中任何普通奖档，包括相同倍率；下一个 UTC 自然日重新累计。",
    },
    jackpot_spend_threshold_coins: {
        summary:
            "同一应用、奖池和用户在一个 UTC 自然日内，每累计实际消费满这个金币数，就产生 1 个待用大奖资格。例如门槛 800,000，消费达到 800,000、1,600,000、2,400,000 时会各产生 1 个。",
        adjust: "调低会更频繁地产生消费大奖资格；调高会要求用户先累计更多消费。资格数量按每个 UTC 自然日独立计算。",
        constraint:
            "启用 dynamic_v3 时必须大于 0。达标当次子抽不能立即使用新资格，从下一子抽开始尝试；大盘、水位、每日次数或任一返奖上限不满足时资格不会作废，而是跨日保留到能够支付。该策略与 48 小时亏损补偿独立，不要求注册满 48 小时，也不检查用户本轮或近 48 小时 RTP。",
    },
    max_single_payout: {
        summary: "限制一抽最多能返多少金币；超过这个金额的奖档当次不会发出。",
        adjust: "调高允许更大的单次奖励，也增加资金风险；调低会直接挡住更多高倍率结果。",
        constraint:
            "启用 V3 时必须大于 0；批量送礼按每个子抽分别判断。奖档超过任一剩余额度会整档挡住，不会裁剪成较小金额。",
    },
    user_hourly_payout_cap: {
        summary: "限制同一用户在当前 UTC 小时内累计最多返多少金币。",
        adjust: "调高允许用户短时间获得更多奖励；调低会更早限制高频送礼用户。",
        constraint: "启用 V3 时必须大于 0；按当前奖池和实际支付时间所在的 UTC 小时计算，到下一小时重新累计。",
    },
    user_daily_payout_cap: {
        summary: "限制同一用户在当前 UTC 自然日内累计最多返多少金币。",
        adjust: "调高允许单个用户一天获得更多奖励；调低会更均匀地分散预算。",
        constraint: "启用 V3 时必须大于 0；按当前奖池和实际支付时间所在的 UTC 日期累计。",
    },
    device_daily_payout_cap: {
        summary: "限制同一设备上的所有账号在一个 UTC 自然日内合计最多返多少金币，用来防止换账号绕过用户上限。",
        adjust: "调高会放宽同设备多账号的总额度；调低会更严格地控制集中领取。",
        constraint: "启用 V3 时必须大于 0，并按当前奖池隔离；必须拿到稳定设备 ID，外部接入也不能用用户 ID 代替。",
    },
    room_hourly_payout_cap: {
        summary: "限制同一房间内所有用户在当前 UTC 小时合计最多返多少金币。",
        adjust: "调高允许热门房间更集中地产生奖励；调低会更早限制单房间的消耗速度。",
        constraint:
            "启用 V3 时必须大于 0，并按当前奖池隔离；外部接口没有真实房间，同一 App 的外部请求会共用一个虚拟房间额度。",
    },
    anchor_daily_payout_cap: {
        summary: "限制同一房主关联的所有房间和用户在一个 UTC 自然日内合计最多返多少金币。",
        adjust: "调高允许奖励更集中在单个主播；调低会把预算分散到更多主播。",
        constraint:
            "启用 V3 时必须大于 0，并按当前奖池隔离；外部接口没有房主，同一 App 的外部请求会共用一个虚拟主播额度。",
    },
    stage_novice: {
        summary:
            "新手阶段使用的一组基础奖档。V2 看累计消费折算次数；V3 中未同时达到普通阶段两项充值门槛的用户都会落在这里。",
        adjust: "可通过倍率组合调整未达到普通门槛用户的体验，但预计平均返还仍要落在允许范围内。",
        constraint:
            "V3 的新手充值门槛固定为 0 / 0；外部接入当前也始终按新手阶段处理。每个阶段都必须保留启用的 0 倍档。",
    },
    stage_normal: {
        summary: "普通阶段使用的一组基础奖档。V2 看累计消费折算次数；V3 必须同时达到这里的 7 日和 30 日最低充值。",
        adjust: "V3 中提高充值门槛会减少进入普通阶段的人数，降低则会扩大范围；V2 由上方等价抽数上限控制阶段。",
        constraint: "V3 中至少一个充值门槛必须大于 0，并且不能高于高阶阶段对应门槛。",
    },
    stage_advanced: {
        summary:
            "高阶阶段使用的一组基础奖档。V2 超过普通折算次数上限后进入；V3 会选择同时满足 7 日和 30 日门槛的最高阶段。",
        adjust: "V3 中提高充值门槛会缩小高阶范围，降低则会扩大；V2 由上方等价抽数上限控制阶段。",
        constraint: "V3 中两项门槛都不能低于普通阶段，且至少一项必须更高。",
    },
    min_recharge_7d_coins: {
        summary: "用户最近 7 个 UTC 自然日（含当天）的充值金币至少达到多少，才满足当前阶段的一半条件。",
        adjust: "调高会减少进入该阶段的人数；调低会扩大范围。",
        constraint: "用户还必须同时达到 30 日门槛；新手阶段固定为 0。外部接入没有可信充值快照时按新手处理。",
    },
    min_recharge_30d_coins: {
        summary: "用户最近 30 个 UTC 自然日（含当天）的充值金币至少达到多少，才满足当前阶段的另一半条件。",
        adjust: "调高会更看重长期充值；调低会让近期活跃用户更容易进入该阶段。",
        constraint: "用户还必须同时达到 7 日门槛；新手阶段固定为 0。",
    },
    tier_multiplier: {
        summary:
            "这一普通奖档返还本抽消费金额的多少倍。0 倍表示没有返还，1 倍表示返还与本抽消费相同。",
        adjust: "调高会增加这一档的奖励金额；系统会自动重算各档基础概率来尽量保持平均返还目标。",
        constraint:
            "不能小于 0；10 倍及以上必须勾选“高倍率安全标记”，实际支付仍不能超过奖池和返奖上限。若同倍率也列为独立大奖，本行仍按普通概率开奖，不需要大奖资格且不累计大奖次数。",
    },
    tier_probability: {
        summary: "表示在当前用户阶段中，通过普通开奖抽到这一档的基础机会，所有启用奖档必须合计 100%。",
        adjust: "该值由平均返还目标和各档倍数自动计算，只读展示；修改倍数或目标后会重新计算。",
        constraint:
            "V3 会按水位和可信充值调整普通非 0 档，并受六项上限限制；V2 保持基础概率，只过滤奖池付不起的档位。相同倍率是否也配置为独立大奖，不会合并或替代这里的概率。",
    },
    tier_high_water_only: {
        summary: "这是发布高倍率基础奖档前的安全确认标记；当前运行逻辑不会根据它判断奖池处于哪种水位。",
        adjust: "勾选或取消不会直接放行、拦截奖档；实际是否能发仍看实时奖池余额和返奖上限。",
        constraint:
            "10 倍及以上必须勾选才能发布。不要把它当成独立的中奖开关；V3 的高低余额变化由上方两组中奖权重控制。",
    },
    experiment_name: {
        summary: "标识这次 AB 实验，显示在配置列表徽章和实验管理弹窗中，方便区分同一奖池的历史实验。",
        adjust: "只影响展示，不影响分流；创建后不可修改。",
        constraint: "必填；建议包含实验目的，例如“提高RTP到98-0715”。",
    },
    experiment_traffic_percent: {
        summary: "控制多大比例的用户进入实验组，按用户 ID 哈希分流，其余用户继续使用对照组（当前最新启用版本）。",
        adjust: "实验期间可随时调整且放量单调：调大不会把已进入实验组的用户踢回对照组。",
        constraint: "0-100；填 0 表示只有白名单用户进入实验组（灰度验证）。暂停时全部流量（含白名单）回到对照组。",
    },
    experiment_whitelist: {
        summary: "白名单用户不参与哈希分流，创建时全部固定进入实验组，用于放量前的内部灰度验证。",
        adjust: "实验进行中可在实验管理里增删白名单，或把某个用户指定到对照组。",
        constraint: "每项为用户 ID 字符串；暂停期间白名单用户同样回到对照组，恢复后回到原分组。",
    },
    tier_enabled: {
        summary: "决定这一档是否参加当前阶段的普通基础抽奖。关闭后不会从普通奖档中选中。",
        adjust: "关闭高倍率档会降低大额结果种类；重新开启后系统会重新计算整组概率。",
        constraint:
            "每个阶段启用档位的概率必须合计 100%，并至少保留一个 0 倍档；关闭这里只影响普通开奖，若同倍率另列为独立大奖，大奖机制仍可单独选中它。",
    },
};

export function LuckyGiftFieldLabel({ field, label }) {
    const help = FIELD_HELP[field];
    if (!help) return label;
    return (
        <Tooltip
            arrow
            describeChild
            enterDelay={120}
            leaveDelay={80}
            placement="top-start"
            slotProps={{ tooltip: { className: "ops-field-help-tooltip" } }}
            title={<FieldHelpContent help={help} label={label} />}
        >
            <span className="ops-field-label" tabIndex={0}>
                <span>{label}</span>
                <span aria-hidden="true" className="ops-field-help-trigger">
                    <HelpOutlineOutlined aria-hidden="true" fontSize="inherit" />
                </span>
            </span>
        </Tooltip>
    );
}

function FieldHelpContent({ help, label }) {
    return (
        <div className="ops-field-help-content" role="note">
            <strong>{label}</strong>
            <dl>
                <div>
                    <dt>作用</dt>
                    <dd>{help.summary}</dd>
                </div>
                <div>
                    <dt>调整影响</dt>
                    <dd>{help.adjust}</dd>
                </div>
                <div>
                    <dt>注意</dt>
                    <dd>{help.constraint}</dd>
                </div>
            </dl>
        </div>
    );
}
