// TeamKpiView：运营中心视图。kpi_view_all 用户看团队排行，普通运营看"我的充值"。
// 数据契约见 SocialBiApp.jsx 的 useSocialBi()：金额为 USD 分、比例为 0-1 小数、缺失为 null（显示 "--"）。

import { useMemo, useState } from "react";
import Button from "@mui/material/Button";
import { useSocialBi } from "../SocialBiApp.jsx";
import { replaceSocialBiKpiTargets } from "../../api.js";
import { appColor } from "../metrics.js";
import { formatCount, formatMoneyMinor, formatRatioPercent, isBlank } from "../format.js";
import { rangeLabel } from "../state.js";
import "./team-kpi-view.css";

const DAY_MS = 86400000;

const DIMENSIONS = [
  { key: "person", label: "按人员×区域" },
  { key: "operatorApp", label: "按人员×App" },
  { key: "team", label: "按部门" }
];

const SORTS = [
  { key: "rate", label: "按达成率" },
  { key: "month", label: "按当月充值" }
];

// 剩余天数：当月结束时间与当前时间的差值向上取整，最小 0。
function remainingDaysOf(monthEndMs) {
  if (isBlank(monthEndMs)) {
    return 0;
  }
  return Math.max(0, Math.ceil((Number(monthEndMs) - Date.now()) / DAY_MS));
}

// 缺口（USD 分）：月目标 - 当月充值；无目标或当月充值缺失时返回 null（不可当 0 计算）。
function gapMinorOf(targetMinor, monthMinor) {
  if (isBlank(targetMinor) || Number(targetMinor) <= 0 || isBlank(monthMinor)) {
    return null;
  }
  return Number(targetMinor) - Number(monthMinor);
}

// 行内"需日均"：缺口 / 剩余天数；已达成显示"已达成"，算不出来显示 "--"。
function dailyNeedText(gapMinor, remainingDays) {
  if (gapMinor === null) {
    return "--";
  }
  if (gapMinor <= 0) {
    return "已达成";
  }
  if (!remainingDays) {
    return "--";
  }
  return formatMoneyMinor(Math.round(gapMinor / remainingDays));
}

// 可空求和：全部缺失保持 null，避免把缺失口径当 0。
function addNullable(sum, value) {
  if (isBlank(value)) {
    return sum;
  }
  return (sum ?? 0) + Number(value);
}

function numberOr(value, fallback) {
  return isBlank(value) ? fallback : Number(value);
}

// 排序：按达成率（缺失排最后），并以当月充值作为次级排序；或直接按当月充值。
function compareRows(left, right, sortKey) {
  if (sortKey === "rate") {
    const diff = numberOr(right.rate, -1) - numberOr(left.rate, -1);
    if (diff) {
      return diff;
    }
  }
  return numberOr(right.monthMinor, -1) - numberOr(left.monthMinor, -1);
}

function operatorLabelOf(item) {
  return item.operator_name || item.operator_account || `#${item.operator_user_id}`;
}

function regionLabelOf(item) {
  return item.region_name || item.region_code || "全部区域";
}

function usdInputText(minor) {
  if (isBlank(minor)) {
    return "0";
  }
  return String(Number(minor) / 100);
}

// 美元输入 → USD 分；空串视为 0（清除目标），NaN/负数返回 null 表示非法。
function usdInputToMinor(text) {
  const numeric = Number(String(text).trim());
  if (!Number.isFinite(numeric) || numeric < 0) {
    return null;
  }
  return Math.round(numeric * 100);
}

export function TeamKpiView() {
  const { appCodes, isLoading, kpi, master, range, refresh } = useSocialBi();
  const [dimension, setDimension] = useState("person");
  const [sortKey, setSortKey] = useState("rate");
  const [isDrawerOpen, setIsDrawerOpen] = useState(false);

  const items = useMemo(() => kpi?.items || [], [kpi]);
  const appRows = useMemo(() => kpi?.app_rows || [], [kpi]);

  const personRows = useMemo(() => {
    const rows = items.map((item, index) => ({
      item,
      key: `${item.operator_user_id}-${item.app_code}-${item.region_id}-${index}`,
      monthMinor: item.month_recharge_usd_minor,
      rate: item.month_attainment_rate
    }));
    rows.sort((left, right) => compareRows(left, right, sortKey));
    return rows;
  }, [items, sortKey]);

  const operatorAppRows = useMemo(() => {
    const rows = (kpi?.operator_app_rows || []).map((item, index) => ({
      item,
      key: `${item.operator_user_id}-${item.app_code}-${index}`,
      monthMinor: item.month_recharge_usd_minor,
      rate: item.month_attainment_rate
    }));
    rows.sort((left, right) => compareRows(left, right, sortKey));
    return rows;
  }, [kpi, sortKey]);

  const teamRows = useMemo(() => {
    const groups = new Map();
    items.forEach((item) => {
      const teamName = item.team || "未分组";
      let group = groups.get(teamName);
      if (!group) {
        group = { members: new Set(), monthMinor: null, rangeMinor: null, targetMinor: null, team: teamName };
        groups.set(teamName, group);
      }
      group.members.add(item.operator_user_id);
      group.rangeMinor = addNullable(group.rangeMinor, item.range_recharge_usd_minor);
      group.monthMinor = addNullable(group.monthMinor, item.month_recharge_usd_minor);
      group.targetMinor = addNullable(group.targetMinor, item.month_target_usd_minor);
    });
    const rows = [...groups.values()].map((group) => ({
      memberCount: group.members.size,
      monthMinor: group.monthMinor,
      rangeMinor: group.rangeMinor,
      rate: group.targetMinor && group.monthMinor !== null ? group.monthMinor / group.targetMinor : null,
      targetMinor: group.targetMinor,
      team: group.team
    }));
    rows.sort((left, right) => compareRows(left, right, sortKey));
    return rows;
  }, [items, sortKey]);

  if (!items.length) {
    return isLoading ? <KpiSkeleton /> : (
      <section className="sbi-card">
        <div className="sbi-empty">
          <strong>暂无充值数据</strong>
          <span>先在用户管理为运营人员分配 App/区域数据范围</span>
        </div>
      </section>
    );
  }

  const summary = kpi.summary || {};
  const canViewAll = master?.permissions?.kpi_view_all !== false;
  const canManage = Boolean(master?.permissions?.kpi_manage);
  const remainingDays = remainingDaysOf(kpi.month_end_ms);

  const heroRate = summary.month_attainment_rate;
  const attainText = isBlank(heroRate) ? "未设目标" : formatRatioPercent(heroRate);
  const ringPct = isBlank(heroRate) ? 0 : Math.max(0, Math.min(100, Number(heroRate) * 100));
  const heroGap = gapMinorOf(summary.month_target_usd_minor, summary.month_recharge_usd_minor);
  const heroGapText = heroGap === null ? "--" : heroGap <= 0 ? "已达成" : formatMoneyMinor(heroGap);
  const heroDailyNeed =
    heroGap !== null && heroGap > 0 && remainingDays > 0 ? formatMoneyMinor(Math.round(heroGap / remainingDays)) : "--";

  return (
    <div className="sbi-kpi-view">
      <section className="sbi-kpi-hero">
        <article className="sbi-card sbi-kpi-hero-main">
          <div className="sbi-card-header">
            <strong>{canViewAll ? "团队充值" : "我的充值"}</strong>
            <small>本月充值目标进度 · {kpi.period_month}</small>
            {!canViewAll ? <span className="sbi-badge">仅展示我负责的范围</span> : null}
          </div>
          <div className="sbi-kpi-hero-body">
            <div
              aria-label={`本月达成率 ${attainText}`}
              className="sbi-kpi-ring"
              role="img"
              style={{ "--sbi-kpi-pct": String(ringPct) }}
            >
              <div className="sbi-kpi-ring-hole">
                <strong className={isBlank(heroRate) ? "is-muted" : undefined}>{attainText}</strong>
                <small>本月达成率</small>
              </div>
            </div>
            <dl className="sbi-kpi-hero-stats">
              <div>
                <dt>当月充值</dt>
                <dd>{formatMoneyMinor(summary.month_recharge_usd_minor)}</dd>
              </div>
              <div>
                <dt>月目标</dt>
                <dd>{formatMoneyMinor(summary.month_target_usd_minor)}</dd>
              </div>
              <div>
                <dt className="sbi-metric-hint" title="月目标 - 当月充值">缺口</dt>
                <dd>{heroGapText}</dd>
              </div>
            </dl>
          </div>
        </article>
        <article className="sbi-card sbi-kpi-mini">
          <span className="sbi-kpi-mini-label">剩余天数</span>
          <strong className="sbi-kpi-mini-value">{remainingDays} 天</strong>
          <small className="sbi-kpi-mini-note">{kpi.period_month}</small>
        </article>
        <article className="sbi-card sbi-kpi-mini">
          <span className="sbi-kpi-mini-label sbi-metric-hint" title="缺口 / 剩余天数">需日均</span>
          <strong className="sbi-kpi-mini-value">{heroDailyNeed}</strong>
          <small className="sbi-kpi-mini-note">达成月目标每日还需充值</small>
        </article>
        <article className="sbi-card sbi-kpi-mini">
          <span className="sbi-kpi-mini-label">区间充值</span>
          <strong className="sbi-kpi-mini-value">{formatMoneyMinor(summary.range_recharge_usd_minor)}</strong>
          <small className="sbi-kpi-mini-note">{rangeLabel(range)}</small>
        </article>
      </section>

      {appRows.length ? <AppTargetStrip appCodes={appCodes} appRows={appRows} remainingDays={remainingDays} /> : null}

      <section className="sbi-card sbi-kpi-board">
        <div className="sbi-card-header">
          <strong>充值排行</strong>
          <small>
            {kpi.period_month} · {formatCount(summary.operator_count)} 名运营
          </small>
          <div className="sbi-card-toolbar">
            <div aria-label="排行维度" className="sbi-seg" role="radiogroup">
              {DIMENSIONS.map((option) => (
                <button
                  aria-checked={dimension === option.key}
                  className={dimension === option.key ? "is-active" : ""}
                  key={option.key}
                  onClick={() => setDimension(option.key)}
                  role="radio"
                  type="button"
                >
                  {option.label}
                </button>
              ))}
            </div>
            <div aria-label="排序方式" className="sbi-seg" role="radiogroup">
              {SORTS.map((option) => (
                <button
                  aria-checked={sortKey === option.key}
                  className={sortKey === option.key ? "is-active" : ""}
                  key={option.key}
                  onClick={() => setSortKey(option.key)}
                  role="radio"
                  type="button"
                >
                  {option.label}
                </button>
              ))}
            </div>
            {canManage ? (
              <Button onClick={() => setIsDrawerOpen(true)} size="small" variant="outlined">
                配置目标
              </Button>
            ) : null}
          </div>
        </div>
        <div className="sbi-table-scroll sbi-kpi-board-scroll">
          {dimension === "person" ? (
            <PersonTable appCodes={appCodes} remainingDays={remainingDays} rows={personRows} />
          ) : dimension === "operatorApp" ? (
            <OperatorAppTable appCodes={appCodes} remainingDays={remainingDays} rows={operatorAppRows} />
          ) : (
            <TeamTable rows={teamRows} />
          )}
        </div>
      </section>

      {isDrawerOpen ? (
        <KpiTargetDrawer
          appCodes={appCodes}
          kpi={kpi}
          onClose={() => setIsDrawerOpen(false)}
          onSaved={() => {
            setIsDrawerOpen(false);
            refresh();
          }}
        />
      ) : null}
    </div>
  );
}

function KpiSkeleton() {
  return (
    <div aria-busy="true" className="sbi-kpi-view">
      <section className="sbi-kpi-hero">
        <article className="sbi-card sbi-kpi-hero-loading">
          <span className="sbi-skeleton sbi-kpi-skeleton-ring" />
          <div className="sbi-kpi-skeleton-lines">
            <span className="sbi-skeleton" style={{ height: 16, width: "72%" }} />
            <span className="sbi-skeleton" style={{ height: 16, width: "56%" }} />
            <span className="sbi-skeleton" style={{ height: 16, width: "64%" }} />
          </div>
        </article>
        {[0, 1, 2].map((index) => (
          <article className="sbi-card sbi-kpi-mini" key={index}>
            <span className="sbi-skeleton" style={{ height: 12, width: 64 }} />
            <span className="sbi-skeleton" style={{ height: 24, width: 112 }} />
            <span className="sbi-skeleton" style={{ height: 10, width: 88 }} />
          </article>
        ))}
      </section>
      <section className="sbi-card">
        <div className="sbi-kpi-table-skeleton">
          {[0, 1, 2, 3, 4].map((index) => (
            <span className="sbi-skeleton" key={index} style={{ height: 18, width: "100%" }} />
          ))}
        </div>
      </section>
    </div>
  );
}

function RankBadge({ rank }) {
  const medal = rank === 1 ? " is-gold" : rank === 2 ? " is-silver" : rank === 3 ? " is-bronze" : "";
  return <span className={`sbi-kpi-rank${medal}`}>{rank}</span>;
}

// 达成率单元格：底槽 + 按达成率填充（超 100% 封顶），数字显示真实值；null 显示"未设目标"。
function AttainmentBar({ rate }) {
  if (isBlank(rate)) {
    return <span className="sbi-kpi-no-target">未设目标</span>;
  }
  const numeric = Number(rate);
  const pct = Math.max(0, Math.min(100, numeric * 100));
  return (
    <span className="sbi-kpi-attain">
      <span aria-hidden="true" className="sbi-kpi-bar">
        <span className={numeric >= 1 ? "sbi-kpi-bar-fill is-done" : "sbi-kpi-bar-fill"} style={{ width: `${pct}%` }} />
      </span>
      <span className="sbi-kpi-attain-value">{formatRatioPercent(rate)}</span>
    </span>
  );
}

// App 总目标进度条：不分运营的全员共同目标（kpi.app_rows）。
function AppTargetStrip({ appCodes, appRows, remainingDays }) {
  return (
    <section aria-label="App 总目标进度" className="sbi-kpi-app-strip">
      {appRows.map((row) => {
        const gap = gapMinorOf(row.month_target_usd_minor, row.month_recharge_usd_minor);
        return (
          <article className="sbi-card sbi-kpi-app-card" key={row.app_code}>
            <div className="sbi-kpi-app-card-head">
              <span className="sbi-kpi-app">
                <span className="sbi-kpi-app-dot" style={{ background: appColor(row.app_code, appCodes) }} />
                <strong>{row.app_name || row.app_code}</strong>
              </span>
              <span className="sbi-badge" title="不分运营的全员共同目标">App 总目标</span>
              {row.data_error ? (
                <span className="sbi-badge is-warn" title={row.data_error}>
                  数据异常
                </span>
              ) : null}
            </div>
            <div className="sbi-kpi-app-card-amounts">
              <strong>{formatMoneyMinor(row.month_recharge_usd_minor)}</strong>
              <small> / 目标 {row.month_target_usd_minor > 0 ? formatMoneyMinor(row.month_target_usd_minor) : "未设"}</small>
            </div>
            <AttainmentBar rate={row.month_attainment_rate} />
            <small className="sbi-kpi-app-card-note">
              {[
                `${formatCount(row.operator_count)} 名运营`,
                `区间充值 ${formatMoneyMinor(row.range_recharge_usd_minor)}`,
                `需日均 ${dailyNeedText(gap, remainingDays)}`
              ].join(" · ")}
            </small>
          </article>
        );
      })}
    </section>
  );
}

// 人员×App 小计表：该运营在此 App 下全部负责区域的合计与其 App 级目标。
function OperatorAppTable({ appCodes, remainingDays, rows }) {
  return (
    <table className="sbi-table">
      <thead>
        <tr>
          <th className="is-left">名次</th>
          <th className="is-left">运营人员</th>
          <th className="is-left">App</th>
          <th className="is-left">负责区域</th>
          <th>
            <span className="sbi-metric-hint" title="所选区间内该运营在此 App 负责范围的充值合计（USD）">区间充值</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="本自然月至今该运营在此 App 负责范围的充值合计（USD）">当月充值</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="该运营在此 App 的月度总目标（USD）">App 月目标</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="当月充值 / App 月目标">达成率</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="缺口 / 剩余天数">需日均</span>
          </th>
        </tr>
      </thead>
      <tbody>
        {rows.map((row, index) => {
          const item = row.item;
          const gap = gapMinorOf(item.month_target_usd_minor, item.month_recharge_usd_minor);
          return (
            <tr key={row.key}>
              <td className="is-left">
                <RankBadge rank={index + 1} />
              </td>
              <td className="is-left">
                <div className="sbi-kpi-person">
                  <span className="sbi-kpi-person-top">
                    <strong>{operatorLabelOf(item)}</strong>
                    {item.data_error ? (
                      <span className="sbi-badge is-warn" title={item.data_error}>
                        数据异常
                      </span>
                    ) : null}
                  </span>
                  <small>{[item.operator_account, item.team || "未分组"].filter(Boolean).join(" · ")}</small>
                </div>
              </td>
              <td className="is-left">
                <span className="sbi-kpi-app">
                  <span className="sbi-kpi-app-dot" style={{ background: appColor(item.app_code, appCodes) }} />
                  {item.app_name || item.app_code}
                </span>
              </td>
              <td className="is-left">
                {item.whole_app_scope ? "全部区域" : item.region_names || `${formatCount(item.region_count)} 个区域`}
              </td>
              <td>{formatMoneyMinor(item.range_recharge_usd_minor)}</td>
              <td>{formatMoneyMinor(item.month_recharge_usd_minor)}</td>
              <td>{formatMoneyMinor(item.month_target_usd_minor)}</td>
              <td>
                <AttainmentBar rate={item.month_attainment_rate} />
              </td>
              <td>{dailyNeedText(gap, remainingDays)}</td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

function PersonTable({ appCodes, remainingDays, rows }) {
  return (
    <table className="sbi-table">
      <thead>
        <tr>
          <th className="is-left">名次</th>
          <th className="is-left">运营人员</th>
          <th className="is-left">App</th>
          <th className="is-left">区域</th>
          <th>
            <span className="sbi-metric-hint" title="所选区间内负责范围的充值合计（USD）">区间充值</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="本自然月至今负责范围的充值合计（USD）">当月充值</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="本月充值目标（USD），由管理员配置">月目标</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="当月充值 / 月目标">达成率</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="缺口 / 剩余天数，达成月目标每日还需充值">需日均</span>
          </th>
        </tr>
      </thead>
      <tbody>
        {rows.map((row, index) => {
          const item = row.item;
          const gap = gapMinorOf(item.month_target_usd_minor, item.month_recharge_usd_minor);
          return (
            <tr key={row.key}>
              <td className="is-left">
                <RankBadge rank={index + 1} />
              </td>
              <td className="is-left">
                <div className="sbi-kpi-person">
                  <span className="sbi-kpi-person-top">
                    <strong>{operatorLabelOf(item)}</strong>
                    {item.data_error ? (
                      <span className="sbi-badge is-warn" title={item.data_error}>
                        数据异常
                      </span>
                    ) : null}
                  </span>
                  <small>{[item.operator_account, item.team || "未分组"].filter(Boolean).join(" · ")}</small>
                </div>
              </td>
              <td className="is-left">
                <span className="sbi-kpi-app">
                  <span className="sbi-kpi-app-dot" style={{ background: appColor(item.app_code, appCodes) }} />
                  {item.app_name || item.app_code}
                </span>
              </td>
              <td className="is-left">{regionLabelOf(item)}</td>
              <td>{formatMoneyMinor(item.range_recharge_usd_minor)}</td>
              <td>{formatMoneyMinor(item.month_recharge_usd_minor)}</td>
              <td>{formatMoneyMinor(item.month_target_usd_minor)}</td>
              <td>
                <AttainmentBar rate={item.month_attainment_rate} />
              </td>
              <td>{dailyNeedText(gap, remainingDays)}</td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

function TeamTable({ rows }) {
  return (
    <table className="sbi-table">
      <thead>
        <tr>
          <th className="is-left">名次</th>
          <th className="is-left">部门</th>
          <th>
            <span className="sbi-metric-hint" title="部门内去重运营人数">人数</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="部门内区间充值合计（USD）">区间充值</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="部门内本月充值合计（USD）">当月充值</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="部门内月目标合计（USD）">月目标</span>
          </th>
          <th>
            <span className="sbi-metric-hint" title="部门当月充值合计 / 月目标合计">达成率</span>
          </th>
        </tr>
      </thead>
      <tbody>
        {rows.map((row, index) => (
          <tr key={row.team}>
            <td className="is-left">
              <RankBadge rank={index + 1} />
            </td>
            <td className="is-left">{row.team}</td>
            <td>{formatCount(row.memberCount)}</td>
            <td>{formatMoneyMinor(row.rangeMinor)}</td>
            <td>{formatMoneyMinor(row.monthMinor)}</td>
            <td>{formatMoneyMinor(row.targetMinor)}</td>
            <td>
              <AttainmentBar rate={row.rate} />
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

function draftInvalid(draft) {
  return usdInputToMinor(draft.monthText) === null || usdInputToMinor(draft.dailyText) === null;
}

function TargetInputCell({ draft, label, onChange }) {
  return (
    <input
      aria-label={label}
      className={usdInputToMinor(draft.value) === null ? "sbi-kpi-input is-invalid" : "sbi-kpi-input"}
      inputMode="decimal"
      min="0"
      onChange={onChange}
      step="0.01"
      type="number"
      value={draft.value}
    />
  );
}

// 配置目标抽屉：三级目标——
// ① App 总目标（不分运营的全员共同目标） ② 运营×App 总目标（该运营在此 App 的总盘子）
// ③ 运营×App×区域 明细目标。月/日目标同时填 0 表示清除该条。
function KpiTargetDrawer({ appCodes, kpi, onClose, onSaved }) {
  const [appDrafts, setAppDrafts] = useState(() =>
    (kpi.app_rows || []).map((row) => ({
      appCode: row.app_code,
      appName: row.app_name || row.app_code,
      dailyText: usdInputText(row.daily_target_usd_minor),
      monthText: usdInputText(row.month_target_usd_minor)
    }))
  );
  // 整 App 范围（region 0）的运营在明细区已有"全部区域"行（同一存储键），此处排除避免同键两处编辑。
  const [groupDrafts, setGroupDrafts] = useState(() =>
    (kpi.operator_app_rows || [])
      .filter((row) => !row.whole_app_scope)
      .map((row) => ({
        appCode: row.app_code,
        appName: row.app_name || row.app_code,
        dailyText: usdInputText(row.daily_target_usd_minor),
        monthText: usdInputText(row.month_target_usd_minor),
        operatorAccount: row.operator_account || "",
        operatorLabel: row.operator_name || row.operator_account || `#${row.operator_user_id}`,
        regionNames: row.region_names || "",
        userId: row.operator_user_id
      }))
  );
  const [regionDrafts, setRegionDrafts] = useState(() =>
    (kpi.items || []).map((item) => ({
      appCode: item.app_code,
      appName: item.app_name || item.app_code,
      dailyText: usdInputText(item.daily_target_usd_minor),
      monthText: usdInputText(item.month_target_usd_minor),
      operatorAccount: item.operator_account || "",
      operatorLabel: operatorLabelOf(item),
      regionId: Number(item.region_id ?? 0),
      regionName: regionLabelOf(item),
      userId: item.operator_user_id
    }))
  );
  const [isSaving, setIsSaving] = useState(false);
  const [saveError, setSaveError] = useState("");

  const hasInvalid = [...appDrafts, ...groupDrafts, ...regionDrafts].some(draftInvalid);
  const hasRows = appDrafts.length > 0 || groupDrafts.length > 0 || regionDrafts.length > 0;

  const updateAt = (setDrafts) => (index, key, value) => {
    setDrafts((current) => current.map((draft, draftIndex) => (draftIndex === index ? { ...draft, [key]: value } : draft)));
  };
  const updateAppDraft = updateAt(setAppDrafts);
  const updateGroupDraft = updateAt(setGroupDrafts);
  const updateRegionDraft = updateAt(setRegionDrafts);

  const handleSave = async () => {
    const items = [
      ...groupDrafts.map((draft) => ({
        appCode: draft.appCode,
        dailyTargetUsdMinor: usdInputToMinor(draft.dailyText),
        periodMonth: kpi.period_month,
        regionId: 0,
        targetUsdMinor: usdInputToMinor(draft.monthText),
        userId: draft.userId
      })),
      ...regionDrafts.map((draft) => ({
        appCode: draft.appCode,
        dailyTargetUsdMinor: usdInputToMinor(draft.dailyText),
        periodMonth: kpi.period_month,
        regionId: draft.regionId,
        targetUsdMinor: usdInputToMinor(draft.monthText),
        userId: draft.userId
      }))
    ];
    const appItems = appDrafts.map((draft) => ({
      appCode: draft.appCode,
      dailyTargetUsdMinor: usdInputToMinor(draft.dailyText),
      periodMonth: kpi.period_month,
      targetUsdMinor: usdInputToMinor(draft.monthText)
    }));
    setIsSaving(true);
    setSaveError("");
    try {
      await replaceSocialBiKpiTargets({ appItems, items });
      onSaved();
    } catch (error) {
      setSaveError(error?.message || "保存失败，请稍后重试");
      setIsSaving(false);
    }
  };

  return (
    <div className="sbi-kpi-drawer-mask" onClick={onClose} role="presentation">
      <aside aria-label="配置充值目标" className="sbi-kpi-drawer" onClick={(event) => event.stopPropagation()} role="dialog">
        <header className="sbi-kpi-drawer-header">
          <div className="sbi-kpi-drawer-title">
            <strong>配置充值目标</strong>
            <small>{kpi.period_month} · App 总目标 / 运营×App / 运营×App×区域 三级 · 月/日目标填 0 表示清除</small>
          </div>
          <button aria-label="关闭" className="sbi-kpi-drawer-close" onClick={onClose} type="button">
            ×
          </button>
        </header>
        <div className="sbi-kpi-drawer-body">
          {hasRows ? (
            <>
              {appDrafts.length ? (
                <section className="sbi-kpi-drawer-section">
                  <h4>App 总目标 <small>不分运营的全员共同目标</small></h4>
                  <div className="sbi-table-scroll">
                    <table className="sbi-table">
                      <thead>
                        <tr>
                          <th className="is-left">App</th>
                          <th>当月目标 ($)</th>
                          <th>当日目标 ($)</th>
                        </tr>
                      </thead>
                      <tbody>
                        {appDrafts.map((draft, index) => (
                          <tr key={draft.appCode}>
                            <td className="is-left">
                              <span className="sbi-kpi-app">
                                <span className="sbi-kpi-app-dot" style={{ background: appColor(draft.appCode, appCodes) }} />
                                {draft.appName}
                              </span>
                            </td>
                            <td>
                              <TargetInputCell
                                draft={{ ...draft, value: draft.monthText }}
                                label={`${draft.appName} App 当月总目标（美元）`}
                                onChange={(event) => updateAppDraft(index, "monthText", event.target.value)}
                              />
                            </td>
                            <td>
                              <TargetInputCell
                                draft={{ ...draft, value: draft.dailyText }}
                                label={`${draft.appName} App 当日总目标（美元）`}
                                onChange={(event) => updateAppDraft(index, "dailyText", event.target.value)}
                              />
                            </td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                </section>
              ) : null}
              {groupDrafts.length ? (
                <section className="sbi-kpi-drawer-section">
                  <h4>运营 × App 总目标 <small>该运营在此 App 全部负责区域的总盘子</small></h4>
                  <div className="sbi-table-scroll">
                    <table className="sbi-table">
                      <thead>
                        <tr>
                          <th className="is-left">运营人员</th>
                          <th className="is-left">App</th>
                          <th className="is-left">负责区域</th>
                          <th>当月目标 ($)</th>
                          <th>当日目标 ($)</th>
                        </tr>
                      </thead>
                      <tbody>
                        {groupDrafts.map((draft, index) => (
                          <tr key={`${draft.userId}-${draft.appCode}`}>
                            <td className="is-left">
                              <div className="sbi-kpi-person">
                                <span className="sbi-kpi-person-top">
                                  <strong>{draft.operatorLabel}</strong>
                                </span>
                                {draft.operatorAccount ? <small>{draft.operatorAccount}</small> : null}
                              </div>
                            </td>
                            <td className="is-left">
                              <span className="sbi-kpi-app">
                                <span className="sbi-kpi-app-dot" style={{ background: appColor(draft.appCode, appCodes) }} />
                                {draft.appName}
                              </span>
                            </td>
                            <td className="is-left">{draft.regionNames}</td>
                            <td>
                              <TargetInputCell
                                draft={{ ...draft, value: draft.monthText }}
                                label={`${draft.operatorLabel} ${draft.appName} 当月总目标（美元）`}
                                onChange={(event) => updateGroupDraft(index, "monthText", event.target.value)}
                              />
                            </td>
                            <td>
                              <TargetInputCell
                                draft={{ ...draft, value: draft.dailyText }}
                                label={`${draft.operatorLabel} ${draft.appName} 当日总目标（美元）`}
                                onChange={(event) => updateGroupDraft(index, "dailyText", event.target.value)}
                              />
                            </td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                </section>
              ) : null}
              {regionDrafts.length ? (
                <section className="sbi-kpi-drawer-section">
                  <h4>运营 × App × 区域 明细目标</h4>
                  <div className="sbi-table-scroll">
                    <table className="sbi-table">
                      <thead>
                        <tr>
                          <th className="is-left">运营人员</th>
                          <th className="is-left">App</th>
                          <th className="is-left">区域</th>
                          <th>当月目标 ($)</th>
                          <th>当日目标 ($)</th>
                        </tr>
                      </thead>
                      <tbody>
                        {regionDrafts.map((draft, index) => (
                          <tr key={`${draft.userId}-${draft.appCode}-${draft.regionId}-${index}`}>
                            <td className="is-left">
                              <div className="sbi-kpi-person">
                                <span className="sbi-kpi-person-top">
                                  <strong>{draft.operatorLabel}</strong>
                                </span>
                                {draft.operatorAccount ? <small>{draft.operatorAccount}</small> : null}
                              </div>
                            </td>
                            <td className="is-left">
                              <span className="sbi-kpi-app">
                                <span className="sbi-kpi-app-dot" style={{ background: appColor(draft.appCode, appCodes) }} />
                                {draft.appName}
                              </span>
                            </td>
                            <td className="is-left">{draft.regionName}</td>
                            <td>
                              <TargetInputCell
                                draft={{ ...draft, value: draft.monthText }}
                                label={`${draft.operatorLabel} ${draft.appName} ${draft.regionName} 当月目标（美元）`}
                                onChange={(event) => updateRegionDraft(index, "monthText", event.target.value)}
                              />
                            </td>
                            <td>
                              <TargetInputCell
                                draft={{ ...draft, value: draft.dailyText }}
                                label={`${draft.operatorLabel} ${draft.appName} ${draft.regionName} 当日目标（美元）`}
                                onChange={(event) => updateRegionDraft(index, "dailyText", event.target.value)}
                              />
                            </td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                </section>
              ) : null}
            </>
          ) : (
            <div className="sbi-empty">
              <strong>暂无可配置的目标行</strong>
              <span>先在用户管理为运营人员分配 App/区域数据范围</span>
            </div>
          )}
        </div>
        <footer className="sbi-kpi-drawer-footer">
          {saveError || hasInvalid ? (
            <span className="sbi-kpi-drawer-error" role="alert">
              {saveError || "目标金额需为非负数字"}
            </span>
          ) : null}
          <Button disabled={isSaving} onClick={onClose} variant="text">
            取消
          </Button>
          <Button disabled={hasInvalid || isSaving || !hasRows} onClick={handleSave} variant="contained">
            {isSaving ? "保存中…" : "保存目标"}
          </Button>
        </footer>
      </aside>
    </div>
  );
}
