import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogTitle from "@mui/material/DialogTitle";
import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField";
import { useState } from "react";
import { Button } from "@/shared/ui/Button.jsx";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import { luckyGiftPoolKey } from "../api.js";
import {
EXPERIMENT_ARM_LABELS,
EXPERIMENT_ARMS,
experimentArmStat,
experimentStatusBadge,
formatTrafficPercent,
isActiveExperiment,
trafficPercentError,
} from "../experimentForm.js";
import { formatNumber, formatPercent } from "../format.js";
import { LuckyGiftPoolActions } from "./LuckyGiftPoolActions.jsx";
import { OpsStatusBadge } from "./OpsStatusBadge.jsx";
// AB 实验管理弹窗:展示两组统计并承载放量、暂停/恢复、白名单、结束实验四类操作。
// 所有请求都由 OpsCenterApp 的回调执行(失败 toast 保留弹窗),这里只负责输入校验与二次确认。
export function LuckyGiftExperimentManageDialog({
busy,
canCreditPool,
canDebitPool,
canManage,
experiment,
onAdjustTraffic,
onClose,
onCreditPool,
onDebitPool,
onEnd,
onRemoveOverride,
onSetStatus,
onUpsertOverride,
poolBalances = [],
poolBusy = false,
statsLoading,
}) {
const [trafficInput, setTrafficInput] = useState(() => String(experiment.treatment_traffic_percent ?? ""));
const [trafficError, setTrafficError] = useState("");
const [overrideUserKey, setOverrideUserKey] = useState("");
const [overrideArm, setOverrideArm] = useState("treatment");
const [overrideError, setOverrideError] = useState("");
const [endConfirmArm, setEndConfirmArm] = useState("");
const paused = experiment.status === "paused";
const completed = experiment.status === "completed";
// completed 只读:历史入口打开时仅展示分组数据与获胜结果,不再提供任何写操作。
const editable = canManage && isActiveExperiment(experiment);
const badge = experimentStatusBadge(experiment);
const overrides = Array.isArray(experiment.overrides) ? experiment.overrides : [];
const armStrategies = EXPERIMENT_ARMS.map((arm) => experimentArmStat(experiment, arm)?.strategy_version).filter(
Boolean,
);
// 水位以 app+pool+strategy 为账本主键,而不是实验 arm。跨策略实验两组各自命中一口水位;
// 同策略实验则必须展示共享语义,避免运营从两张卡各补一次而误以为资金彼此隔离。
const sharedStrategyPool = armStrategies.length === EXPERIMENT_ARMS.length && new Set(armStrategies).size === 1;
const submitTraffic = (event) => {
event.preventDefault();
const error = trafficPercentError(trafficInput);
setTrafficError(error);
if (error) {
return;
}
onAdjustTraffic(Number(trafficInput));
};
const submitOverride = (event) => {
event.preventDefault();
const userKey = overrideUserKey.trim();
if (!userKey) {
setOverrideError("请输入用户 ID");
return;
}
setOverrideError("");
setOverrideUserKey("");
onUpsertOverride({ arm: overrideArm, userKey });
};
return (
);
}
function ExperimentArmCard({
arm,
busy,
canCreditPool,
canDebitPool,
canEnd,
experiment,
onCreditPool,
onDebitPool,
onPickWinner,
poolBalances,
poolBusy,
statsLoading,
}) {
const stat = experimentArmStat(experiment, arm);
const ruleVersion = arm === "control" ? experiment.control_rule_version : experiment.treatment_rule_version;
const isWinner = experiment.status === "completed" && experiment.winner_arm === arm;
const strategyVersion = stat?.strategy_version || "";
const poolIdentity = {
app_code: experiment.app_code,
pool_id: experiment.pool_id,
strategy_version: strategyVersion,
};
const pool = strategyVersion
? (poolBalances || []).find((item) => luckyGiftPoolKey(item) === luckyGiftPoolKey(poolIdentity))
: null;
// 保留完整策略身份作为禁用按钮的 aria/tooltip 上下文;若水位接口暂未返回该账本,
// LuckyGiftPoolActions 会禁止提交,不能用 0 余额伪装成已加载的可调整奖池。
const poolActionTarget = pool || poolIdentity;
// 统计尚未返回时明确展示“加载中”,避免运营把未加载误读成 0(0 消费也是有效实验结论)。
const statValue = (value, format) => (stat ? format(value) : statsLoading ? "加载中" : "-");
const poolValue = (value) => (pool ? formatNumber(value) : statsLoading ? "加载中" : "-");
return (
{strategyVersion ? (